netcmd: domain: tests for auth silo command line tools
authorRob van der Linde <rob@catalyst.net.nz>
Tue, 16 May 2023 00:22:25 +0000 (12:22 +1200)
committerAndrew Bartlett <abartlet@samba.org>
Sun, 25 Jun 2023 23:29:32 +0000 (23:29 +0000)
Signed-off-by: Rob van der Linde <rob@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
Reviewed-by: Joseph Sutton <josephsutton@catalyst.net.nz>
python/samba/tests/samba_tool/domain_auth_base.py [new file with mode: 0644]
python/samba/tests/samba_tool/domain_auth_policy.py [new file with mode: 0644]
python/samba/tests/samba_tool/domain_auth_silo.py [new file with mode: 0644]
selftest/knownfail.d/silo-client-tool [new file with mode: 0644]
source4/selftest/tests.py

diff --git a/python/samba/tests/samba_tool/domain_auth_base.py b/python/samba/tests/samba_tool/domain_auth_base.py
new file mode 100644 (file)
index 0000000..43795dd
--- /dev/null
@@ -0,0 +1,199 @@
+# Unix SMB/CIFS implementation.
+#
+# Base class for samba-tool domain auth policy and silo commands
+#
+# Copyright (C) Catalyst.Net Ltd. 2023
+#
+# Written by Rob van der Linde <rob@catalyst.net.nz>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+import os
+
+from ldb import SCOPE_ONELEVEL
+
+from .base import SambaToolCmdTest
+
+
+class BaseAuthCmdTest(SambaToolCmdTest):
+    def setUp(self):
+        super().setUp()
+        self.host = "ldap://{DC_SERVER}".format(**os.environ)
+        self.creds = "-U{DC_USERNAME}%{DC_PASSWORD}".format(**os.environ)
+        self.samdb = self.getSamDB("-H", self.host, self.creds)
+
+        # Generate some test data.
+        self.policies = []
+        self.create_authentication_policy(name="Single Policy")
+        self.create_authentication_policy(name="User Policy")
+        self.create_authentication_policy(name="Service Policy")
+        self.create_authentication_policy(name="Computer Policy")
+
+        self.silos = []
+        self.create_authentication_silo(name="Developers",
+                                        description="Developers, Developers",
+                                        policy="Single Policy")
+        self.create_authentication_silo(name="Managers",
+                                        description="Managers",
+                                        policy="Single Policy")
+        self.create_authentication_silo(name="QA",
+                                        description="Quality Assurance",
+                                        user_policy="User Policy",
+                                        service_policy="Service Policy",
+                                        computer_policy="Computer Policy")
+
+    def tearDown(self):
+        """Remove data created by setUp."""
+        for policy in self.policies:
+            self.delete_authentication_policy(policy, force=True)
+
+        for silo in self.silos:
+            self.delete_authentication_silo(silo, force=True)
+
+        super().tearDown()
+
+    def get_services_dn(self):
+        """Returns Services DN."""
+        services_dn = self.samdb.get_config_basedn()
+        services_dn.add_child("CN=Services")
+        return services_dn
+
+    def get_authn_configuration_dn(self):
+        """Returns AuthN Configuration DN."""
+        authn_policy_configuration = self.get_services_dn()
+        authn_policy_configuration.add_child("CN=AuthN Policy Configuration")
+        return authn_policy_configuration
+
+    def get_authn_silos_dn(self):
+        """Returns AuthN Silos DN."""
+        authn_silos_dn = self.get_authn_configuration_dn()
+        authn_silos_dn.add_child("CN=AuthN Silos")
+        return authn_silos_dn
+
+    def get_authn_policies_dn(self):
+        """Returns AuthN Policies DN."""
+        authn_policies_dn = self.get_authn_configuration_dn()
+        authn_policies_dn.add_child("CN=AuthN Policies")
+        return authn_policies_dn
+
+    def _run(self, *argv):
+        """Override _run, so we don't always have to pass host and creds."""
+        args = list(argv)
+        args.extend(["-H", self.host, self.creds])
+        return super()._run(*args)
+
+    runcmd = _run
+    runsubcmd = _run
+
+    def create_authentication_policy(self, name, description=None, audit=False,
+                                     protect=False):
+        """Create an authentication policy."""
+
+        # base command for create authentication policy
+        cmd = ["domain", "auth", "policy", "create", "--name", name]
+
+        # optional attributes
+        if description is not None:
+            cmd.append(f"--description={description}")
+        if audit:
+            cmd.append("--audit")
+        if protect:
+            cmd.append("--protect")
+
+        # Run command and store name in self.silos for tearDown to clean up.
+        result, out, err = self.runcmd(*cmd)
+        self.assertIsNone(result, msg=err)
+        self.assertTrue(out.startswith("Created authentication policy"))
+        self.policies.append(name)
+        return name
+
+    def delete_authentication_policy(self, name, force=False):
+        """Delete authentication policy by name."""
+        cmd = ["domain", "auth", "policy", "delete", "--name", name]
+
+        # Force-delete protected authentication policy.
+        if force:
+            cmd.append("--force")
+
+        result, out, err = self.runcmd(*cmd)
+        self.assertIsNone(result, msg=err)
+        self.assertIn("Deleted authentication policy", out)
+
+    def create_authentication_silo(self, name, description=None, policy=None,
+                                   user_policy=None, service_policy=None,
+                                   computer_policy=None, audit=False,
+                                   protect=False):
+        """Create an authentication silo using the samba-tool command."""
+
+        # Base command for create authentication policy.
+        cmd = ["domain", "auth", "silo", "create", "--name", name]
+
+        # If --policy is present, use a singular authentication policy.
+        # otherwise use --user-policy, --service-policy, --computer-policy
+        if policy is not None:
+            cmd += ["--policy", policy]
+        else:
+            cmd += ["--user-policy", user_policy,
+                    "--service-policy", service_policy,
+                    "--computer-policy", computer_policy]
+
+        # Other optional attributes.
+        if description is not None:
+            cmd.append(f"--description={description}")
+        if protect:
+            cmd.append("--protect")
+        if audit:
+            cmd.append("--audit")
+
+        # Run command and store name in self.silos for tearDown to clean up.
+        result, out, err = self.runcmd(*cmd)
+        self.assertIsNone(result, msg=err)
+        self.assertTrue(out.startswith("Created authentication silo"))
+        self.silos.append(name)
+        return name
+
+    def delete_authentication_silo(self, name, force=False):
+        """Delete authentication silo by name."""
+        cmd = ["domain", "auth", "silo", "delete", "--name", name]
+
+        # Force-delete protected authentication silo.
+        if force:
+            cmd.append("--force")
+
+        result, out, err = self.runcmd(*cmd)
+        self.assertIsNone(result, msg=err)
+        self.assertIn("Deleted authentication silo", out)
+
+    def get_authentication_silo(self, name):
+        """Get authentication silo by name."""
+        authn_silos_dn = self.get_authn_silos_dn()
+
+        result = self.samdb.search(base=authn_silos_dn,
+                                   scope=SCOPE_ONELEVEL,
+                                   expression=f"(CN={name})")
+
+        if len(result) == 1:
+            return result[0]
+
+    def get_authentication_policy(self, name):
+        """Get authentication policy by name."""
+        authn_policies_dn = self.get_authn_policies_dn()
+
+        result = self.samdb.search(base=authn_policies_dn,
+                                   scope=SCOPE_ONELEVEL,
+                                   expression=f"(CN={name})")
+
+        if len(result) == 1:
+            return result[0]
diff --git a/python/samba/tests/samba_tool/domain_auth_policy.py b/python/samba/tests/samba_tool/domain_auth_policy.py
new file mode 100644 (file)
index 0000000..d73af18
--- /dev/null
@@ -0,0 +1,607 @@
+# Unix SMB/CIFS implementation.
+#
+# Tests for samba-tool domain auth policy command
+#
+# Copyright (C) Catalyst.Net Ltd. 2023
+#
+# Written by Rob van der Linde <rob@catalyst.net.nz>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+import json
+from optparse import OptionValueError
+from unittest.mock import patch
+
+from ldb import LdbError
+from samba.netcmd import CommandError
+from samba.samdb import SamDB
+from samba.sd_utils import SDUtils
+
+from .domain_auth_base import BaseAuthCmdTest
+
+
+class AuthPolicyCmdTestCase(BaseAuthCmdTest):
+
+    def test_authentication_policy_list(self):
+        """Test listing authentication policies in list format."""
+        result, out, err = self.runcmd("domain", "auth", "policy", "list")
+        self.assertIsNone(result, msg=err)
+
+        # Check each authentication policy we created is there.
+        for policy in self.policies:
+            self.assertIn(policy, out)
+
+    def test_authentication_policy_list_json(self):
+        """Test listing authentication policies in JSON format."""
+        result, out, err = self.runcmd("domain", "auth", "policy",
+                                       "list", "--json")
+        self.assertIsNone(result, msg=err)
+
+        # we should get valid json
+        policies = json.loads(out)
+
+        # each policy in self.policies must be present
+        for name in self.policies:
+            policy = policies[name]
+            self.assertIn("name", policy)
+            self.assertIn("msDS-AuthNPolicy", list(policy["objectClass"]))
+            self.assertIn("msDS-AuthNPolicyEnforced", policy)
+            self.assertIn("msDS-StrongNTLMPolicy", policy)
+            self.assertIn("objectGUID", policy)
+
+    def test_authentication_policy_view(self):
+        """Test viewing a single authentication policy."""
+        result, out, err = self.runcmd("domain", "auth", "policy", "view",
+                                       "--name", "Single Policy")
+        self.assertIsNone(result, msg=err)
+
+        # we should get valid json
+        policy = json.loads(out)
+
+        # check a few fields only
+        self.assertEqual(policy["cn"], "Single Policy")
+        self.assertEqual(policy["msDS-AuthNPolicyEnforced"], True)
+
+    def test_authentication_policy_view_notfound(self):
+        """Test viewing an authentication policy that doesn't exist."""
+        result, out, err = self.runcmd("domain", "auth", "policy", "view",
+                                       "--name", "doesNotExist")
+        self.assertEqual(result, -1)
+        self.assertIn("Authentication policy doesNotExist not found.", err)
+
+    def test_authentication_policy_view_name_required(self):
+        """Test view authentication policy without --name argument."""
+        result, out, err = self.runcmd("domain", "auth", "policy", "view")
+        self.assertEqual(result, -1)
+        self.assertIn("Argument --name is required.", err)
+
+    def test_authentication_policy_create(self):
+        """Test creating a new authentication policy."""
+        result, out, err = self.runcmd("domain", "auth", "policy", "create",
+                                       "--name", "createTest")
+        self.assertIsNone(result, msg=err)
+
+        # Check policy that was created
+        policy = self.get_authentication_policy("createTest")
+        self.assertEqual(str(policy["cn"]), "createTest")
+        self.assertEqual(str(policy["msDS-AuthNPolicyEnforced"]), "TRUE")
+
+    def test_authentication_policy_create_description(self):
+        """Test creating a new authentication policy with description set."""
+        result, out, err = self.runcmd("domain", "auth", "policy", "create",
+                                       "--name", "descriptionTest",
+                                       "--description", "Custom Description")
+        self.assertIsNone(result, msg=err)
+
+        # Check policy description
+        policy = self.get_authentication_policy("descriptionTest")
+        self.assertEqual(str(policy["cn"]), "descriptionTest")
+        self.assertEqual(str(policy["description"]), "Custom Description")
+
+    def test_authentication_policy_create_user_tgt_lifetime(self):
+        """Test create a new authentication policy with --user-tgt-lifetime.
+
+        Also checks the upper and lower bounds are handled.
+        """
+        result, out, err = self.runcmd("domain", "auth", "policy", "create",
+                                       "--name", "userTGTLifetime",
+                                       "--user-tgt-lifetime", "60")
+        self.assertIsNone(result, msg=err)
+
+        # Check policy fields.
+        policy = self.get_authentication_policy("userTGTLifetime")
+        self.assertEqual(str(policy["cn"]), "userTGTLifetime")
+        self.assertEqual(str(policy["msDS-UserTGTLifetime"]), "60")
+
+        # check lower bounds (45)
+        with self.assertRaises(CommandError) as e:
+            self.runcmd("domain", "auth", "policy", "create",
+                        "--name", "userTGTLifetimeLower",
+                        "--user-tgt-lifetime", "44")
+
+        self.assertIn("--user-tgt-lifetime must be between 45 and 2147483647",
+                      str(e.exception))
+
+        # check upper bounds (2147483647)
+        with self.assertRaises(CommandError) as e:
+            self.runcmd("domain", "auth", "policy", "create",
+                        "--name", "userTGTLifetimeUpper",
+                        "--user-tgt-lifetime", "2147483648")
+
+        self.assertIn("--user-tgt-lifetime must be between 45 and 2147483647",
+                      str(e.exception))
+
+    def test_authentication_policy_create_service_tgt_lifetime(self):
+        """Test create a new authentication policy with --service-tgt-lifetime.
+
+        Also checks the upper and lower bounds are handled.
+        """
+        result, out, err = self.runcmd("domain", "auth", "policy", "create",
+                                       "--name", "serviceTGTLifetime",
+                                       "--service-tgt-lifetime", "60")
+        self.assertIsNone(result, msg=err)
+
+        # Check policy fields.
+        policy = self.get_authentication_policy("serviceTGTLifetime")
+        self.assertEqual(str(policy["cn"]), "serviceTGTLifetime")
+        self.assertEqual(str(policy["msDS-ServiceTGTLifetime"]), "60")
+
+        # check lower bounds (45)
+        with self.assertRaises(CommandError) as e:
+            self.runcmd("domain", "auth", "policy", "create",
+                        "--name", "serviceTGTLifetimeLower",
+                        "--service-tgt-lifetime", "44")
+
+        self.assertIn("--service-tgt-lifetime must be between 45 and 2147483647",
+                      str(e.exception))
+
+        # check upper bounds (2147483647)
+        with self.assertRaises(CommandError) as e:
+            self.runcmd("domain", "auth", "policy", "create",
+                        "--name", "serviceTGTLifetimeUpper",
+                        "--service-tgt-lifetime", "2147483648")
+
+        self.assertIn("--service-tgt-lifetime must be between 45 and 2147483647",
+                      str(e.exception))
+
+    def test_authentication_policy_create_computer_tgt_lifetime(self):
+        """Test create a new authentication policy with --computer-tgt-lifetime.
+
+        Also checks the upper and lower bounds are handled.
+        """
+        result, out, err = self.runcmd("domain", "auth", "policy", "create",
+                                       "--name", "computerTGTLifetime",
+                                       "--computer-tgt-lifetime", "60")
+        self.assertIsNone(result, msg=err)
+
+        # Check policy fields.
+        policy = self.get_authentication_policy("computerTGTLifetime")
+        self.assertEqual(str(policy["cn"]), "computerTGTLifetime")
+        self.assertEqual(str(policy["msDS-ComputerTGTLifetime"]), "60")
+
+        # check lower bounds (45)
+        with self.assertRaises(CommandError) as e:
+            self.runcmd("domain", "auth", "policy", "create",
+                        "--name", "computerTGTLifetimeLower",
+                        "--computer-tgt-lifetime", "44")
+
+        self.assertIn("--computer-tgt-lifetime must be between 45 and 2147483647",
+                      str(e.exception))
+
+        # check upper bounds (2147483647)
+        with self.assertRaises(CommandError) as e:
+            self.runcmd("domain", "auth", "policy", "create",
+                        "--name", "computerTGTLifetimeUpper",
+                        "--computer-tgt-lifetime", "2147483648")
+
+        self.assertIn("--computer-tgt-lifetime must be between 45 and 2147483647",
+                      str(e.exception))
+
+    def test_authentication_policy_create_already_exists(self):
+        """Test creating a new authentication policy that already exists."""
+        result, out, err = self.runcmd("domain", "auth", "policy", "create",
+                                       "--name", "Single Policy")
+        self.assertEqual(result, -1)
+        self.assertIn("Authentication policy Single Policy already exists", err)
+
+    def test_authentication_policy_create_name_missing(self):
+        """Test create authentication policy without --name argument."""
+        result, out, err = self.runcmd("domain", "auth", "policy", "create")
+        self.assertEqual(result, -1)
+        self.assertIn("Argument --name is required.", err)
+
+    def test_authentication_policy_create_audit(self):
+        """Test create authentication policy with --audit flag."""
+        result, out, err = self.runcmd("domain", "auth", "policy", "create",
+                                       "--name", "auditPolicy",
+                                       "--audit")
+        self.assertIsNone(result, msg=err)
+
+        # fetch and check policy
+        policy = self.get_authentication_policy("auditPolicy")
+        self.assertEqual(str(policy["msDS-AuthNPolicyEnforced"]), "FALSE")
+
+    def test_authentication_policy_create_enforce(self):
+        """Test create authentication policy with --enforce flag."""
+        result, out, err = self.runcmd("domain", "auth", "policy", "create",
+                                       "--name", "enforcePolicy",
+                                       "--enforce")
+        self.assertIsNone(result, msg=err)
+
+        # fetch and check policy
+        policy = self.get_authentication_policy("enforcePolicy")
+        self.assertEqual(str(policy["msDS-AuthNPolicyEnforced"]), "TRUE")
+
+    def test_authentication_policy_create_audit_enforce_together(self):
+        """Test create auth policy using both --audit and --enforce."""
+        result, out, err = self.runcmd("domain", "auth", "policy", "create",
+                                       "--name", "enforceTogether",
+                                       "--audit", "--enforce")
+        self.assertEqual(result, -1)
+        self.assertIn("--audit and --enforce cannot be used together.", err)
+
+    def test_authentication_policy_create_protect_unprotect_together(self):
+        """Test create authentication policy using --protect and --unprotect."""
+        result, out, err = self.runcmd("domain", "auth", "policy", "create",
+                                       "--name", "protectTogether",
+                                       "--protect", "--unprotect")
+        self.assertEqual(result, -1)
+        self.assertIn("--protect and --unprotect cannot be used together.", err)
+
+    def test_authentication_policy_create_fails(self):
+        """Test creating an authentication policy, but it fails."""
+        # Raise LdbError when ldb.add() is called.
+        with patch.object(SamDB, "add") as add_mock:
+            add_mock.side_effect = LdbError("Custom error message")
+            result, out, err = self.runcmd("domain", "auth", "policy", "create",
+                                           "--name", "createFails")
+            self.assertEqual(result, -1)
+            self.assertIn("ERROR: Custom error message", err)
+
+    def test_authentication_policy_modify_description(self):
+        """Test modifying an authentication policy description."""
+        result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+                                       "--name", "Single Policy",
+                                       "--description", "NewDescription")
+        self.assertIsNone(result, msg=err)
+
+        # Verify fields were changed.
+        policy = self.get_authentication_policy("Single Policy")
+        self.assertEqual(str(policy["description"]), "NewDescription")
+
+    def test_authentication_policy_modify_strong_ntlm_policy(self):
+        """Test modify strong ntlm policy on the authentication policy."""
+        result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+                                       "--name", "Single Policy",
+                                       "--strong-ntlm-policy", "Required")
+        self.assertIsNone(result, msg=err)
+
+        # Verify fields were changed.
+        policy = self.get_authentication_policy("Single Policy")
+        self.assertEqual(str(policy["msDS-StrongNTLMPolicy"]), "2")
+
+        # Check an invalid choice.
+        with self.assertRaises((OptionValueError, SystemExit)):
+            self.runcmd("domain", "auth", "policy", "modify",
+                        "--name", "Single Policy",
+                        "--strong-ntlm-policy", "Invalid")
+
+        # It is difficult to test the error message text for invalid
+        # choices because inside optparse it will raise OptionValueError
+        # followed by raising SystemExit(2).
+
+    def test_authentication_policy_modify_user_tgt_lifetime(self):
+        """Test modifying a authentication policy --user-tgt-lifetime.
+
+        This includes checking the upper and lower bounds.
+        """
+        result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+                                       "--name", "Single Policy",
+                                       "--user-tgt-lifetime", "120")
+        self.assertIsNone(result, msg=err)
+
+        # Verify field was changed.
+        policy = self.get_authentication_policy("Single Policy")
+        self.assertEqual(str(policy["msDS-UserTGTLifetime"]), "120")
+
+        # check lower bounds (45)
+        with self.assertRaises(CommandError) as e:
+            self.runcmd("domain", "auth", "policy", "modify",
+                        "--name", "Single Policy",
+                        "--user-tgt-lifetime", "44")
+
+        self.assertIn("--user-tgt-lifetime must be between 45 and 2147483647",
+                      str(e.exception))
+
+        # check upper bounds (2147483647)
+        with self.assertRaises(CommandError) as e:
+            self.runcmd("domain", "auth", "policy", "modify",
+                        "--name", "Single Policy",
+                        "--user-tgt-lifetime", "2147483648")
+
+        self.assertIn("-user-tgt-lifetime must be between 45 and 2147483647",
+                      str(e.exception))
+
+    def test_authentication_policy_modify_service_tgt_lifetime(self):
+        """Test modifying a authentication policy --service-tgt-lifetime.
+
+        This includes checking the upper and lower bounds.
+        """
+        result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+                                       "--name", "Single Policy",
+                                       "--service-tgt-lifetime", "120")
+        self.assertIsNone(result, msg=err)
+
+        # Verify field was changed.
+        policy = self.get_authentication_policy("Single Policy")
+        self.assertEqual(str(policy["msDS-ServiceTGTLifetime"]), "120")
+
+        # check lower bounds (45)
+        with self.assertRaises(CommandError) as e:
+            self.runcmd("domain", "auth", "policy", "modify",
+                        "--name", "Single Policy",
+                        "--service-tgt-lifetime", "44")
+
+        self.assertIn("--service-tgt-lifetime must be between 45 and 2147483647",
+                      str(e.exception))
+
+        # check upper bounds (2147483647)
+        with self.assertRaises(CommandError) as e:
+            self.runcmd("domain", "auth", "policy", "modify",
+                        "--name", "Single Policy",
+                        "--service-tgt-lifetime", "2147483648")
+
+        self.assertIn("--service-tgt-lifetime must be between 45 and 2147483647",
+                      str(e.exception))
+
+    def test_authentication_policy_modify_computer_tgt_lifetime(self):
+        """Test modifying a authentication policy --computer-tgt-lifetime.
+
+        This includes checking the upper and lower bounds.
+        """
+        result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+                                       "--name", "Single Policy",
+                                       "--computer-tgt-lifetime", "120")
+        self.assertIsNone(result, msg=err)
+
+        # Verify field was changed.
+        policy = self.get_authentication_policy("Single Policy")
+        self.assertEqual(str(policy["msDS-ComputerTGTLifetime"]), "120")
+
+        # check lower bounds (45)
+        with self.assertRaises(CommandError) as e:
+            self.runcmd("domain", "auth", "policy", "modify",
+                        "--name", "Single Policy",
+                        "--computer-tgt-lifetime", "44")
+
+        self.assertIn("--computer-tgt-lifetime must be between 45 and 2147483647",
+                      str(e.exception))
+
+        # check upper bounds (2147483647)
+        with self.assertRaises(CommandError) as e:
+            self.runcmd("domain", "auth", "policy", "modify",
+                        "--name", "Single Policy",
+                        "--computer-tgt-lifetime", "2147483648")
+
+        self.assertIn("--computer-tgt-lifetime must be between 45 and 2147483647",
+                      str(e.exception))
+
+    def test_authentication_policy_modify_name_missing(self):
+        """Test modify authentication but the --name argument is missing."""
+        result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+                                       "--description", "NewDescription")
+        self.assertEqual(result, -1)
+        self.assertIn("Argument --name is required.", err)
+
+    def test_authentication_policy_modify_notfound(self):
+        """Test modify an authentication silo that doesn't exist."""
+        result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+                                       "--name", "doesNotExist",
+                                       "--description", "NewDescription")
+        self.assertEqual(result, -1)
+        self.assertIn("ERROR: Authentication policy doesNotExist not found.",
+                      err)
+
+    def test_authentication_policy_modify_audit_enforce(self):
+        """Test modify authentication policy using --audit and --enforce."""
+        # Change to audit, the default is --enforce.
+        result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+                                       "--name", "Single Policy",
+                                       "--audit")
+        self.assertIsNone(result, msg=err)
+
+        # Check that the policy was changed to --audit.
+        policy = self.get_authentication_policy("Single Policy")
+        self.assertEqual(str(policy["msDS-AuthNPolicyEnforced"]), "FALSE")
+
+        result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+                                       "--name", "Single Policy",
+                                       "--enforce")
+        self.assertIsNone(result, msg=err)
+
+        # Check if the policy was changed back to --enforce.
+        policy = self.get_authentication_policy("Single Policy")
+        self.assertEqual(str(policy["msDS-AuthNPolicyEnforced"]), "TRUE")
+
+    def test_authentication_policy_modify_protect_unprotect(self):
+        """Test modify authentication policy using --protect and --unprotect."""
+        utils = SDUtils(self.samdb)
+        result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+                                       "--name", "Single Policy",
+                                       "--protect")
+        self.assertIsNone(result, msg=err)
+
+        # Check that claim type was protected.
+        policy = self.get_authentication_policy("Single Policy")
+        desc = utils.get_sd_as_sddl(policy["dn"])
+        self.assertIn("(D;;DTSD;;;WD)", desc)
+
+        result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+                                       "--name", "Single Policy",
+                                       "--unprotect")
+        self.assertIsNone(result, msg=err)
+
+        # Check that claim type was unprotected.
+        policy = self.get_authentication_policy("Single Policy")
+        desc = utils.get_sd_as_sddl(policy["dn"])
+        self.assertNotIn("(D;;DTSD;;;WD)", desc)
+
+    def test_authentication_policy_modify_audit_enforce_together(self):
+        """Test modify auth policy using both --audit and --enforce."""
+        result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+                                       "--name", "Single Policy",
+                                       "--audit", "--enforce")
+        self.assertEqual(result, -1)
+        self.assertIn("--audit and --enforce cannot be used together.", err)
+
+    def test_authentication_policy_modify_protect_unprotect_together(self):
+        """Test modify authentication policy using --protect and --unprotect."""
+        result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+                                       "--name", "Single Policy",
+                                       "--protect", "--unprotect")
+        self.assertEqual(result, -1)
+        self.assertIn("--protect and --unprotect cannot be used together.", err)
+
+    def test_authentication_policy_modify_fails(self):
+        """Test modifying an authentication policy, but it fails."""
+        # Raise LdbError when ldb.add() is called.
+        with patch.object(SamDB, "modify") as modify_mock:
+            modify_mock.side_effect = LdbError("Custom error message")
+            result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+                                           "--name", "Single Policy",
+                                           "--description", "New description")
+            self.assertEqual(result, -1)
+            self.assertIn("ERROR: Custom error message", err)
+
+    def test_authentication_policy_delete(self):
+        """Test deleting an authentication policy that is not protected."""
+        # Create non-protected authentication policy.
+        result, out, err = self.runcmd("domain", "auth", "policy", "create",
+                                       "--name=deleteTest")
+        self.assertIsNone(result, msg=err)
+        policy = self.get_authentication_policy("deleteTest")
+        self.assertIsNotNone(policy)
+
+        # Do the deletion.
+        result, out, err = self.runcmd("domain", "auth", "policy", "delete",
+                                       "--name", "deleteTest")
+        self.assertIsNone(result, msg=err)
+
+        # Authentication policy shouldn't exist anymore.
+        policy = self.get_authentication_policy("deleteTest")
+        self.assertIsNone(policy)
+
+    def test_authentication_policy_delete_protected(self):
+        """Test deleting a protected auth policy, with and without --force."""
+        # Create protected authentication policy.
+        result, out, err = self.runcmd("domain", "auth", "policy", "create",
+                                       "--name=deleteProtected",
+                                       "--protect")
+        self.assertIsNone(result, msg=err)
+        policy = self.get_authentication_policy("deleteProtected")
+        self.assertIsNotNone(policy)
+
+        # Do the deletion.
+        result, out, err = self.runcmd("domain", "auth", "policy", "delete",
+                                       "--name=deleteProtected")
+        self.assertEqual(result, -1)
+
+        # Authentication silo should still exist.
+        policy = self.get_authentication_policy("deleteProtected")
+        self.assertIsNotNone(policy)
+
+        # Try a force delete instead.
+        result, out, err = self.runcmd("domain", "auth", "policy", "delete",
+                                       "--name=deleteProtected", "--force")
+        self.assertIsNone(result, msg=err)
+
+        # Authentication silo shouldn't exist anymore.
+        policy = self.get_authentication_policy("deleteProtected")
+        self.assertIsNone(policy)
+
+    def test_authentication_policy_delete_notfound(self):
+        """Test deleting an authentication policy that doesn't exist."""
+        result, out, err = self.runcmd("domain", "auth", "policy", "delete",
+                                       "--name", "doesNotExist")
+        self.assertEqual(result, -1)
+        self.assertIn("Authentication policy doesNotExist not found.", err)
+
+    def test_authentication_policy_delete_name_required(self):
+        """Test deleting an authentication policy without --name argument."""
+        result, out, err = self.runcmd("domain", "auth", "policy", "delete")
+        self.assertEqual(result, -1)
+        self.assertIn("Argument --name is required.", err)
+
+    def test_authentication_policy_delete_force_fails(self):
+        """Test deleting an authentication policy with --force, but it fails."""
+        # Create protected authentication policy.
+        result, out, err = self.runcmd("domain", "auth", "policy", "create",
+                                       "--name=deleteForceFail",
+                                       "--protect")
+        self.assertIsNone(result, msg=err)
+        policy = self.get_authentication_policy("deleteForceFail")
+        self.assertIsNotNone(policy)
+
+        # Try delete with --force.
+        # Patch SDUtils.dacl_delete_aces with a Mock that raises LdbError.
+        with patch.object(SDUtils, "dacl_delete_aces") as delete_mock:
+            delete_mock.side_effect = LdbError("Custom error message")
+            result, out, err = self.runcmd("domain", "auth", "policy", "delete",
+                                           "--name", "deleteForceFail",
+                                           "--force")
+            self.assertEqual(result, -1)
+            self.assertIn("ERROR: Custom error message", err)
+
+    def test_authentication_policy_delete_fails(self):
+        """Test deleting an authentication policy, but it fails."""
+        # Create regular authentication policy.
+        result, out, err = self.runcmd("domain", "auth", "policy", "create",
+                                       "--name=regularPolicy")
+        self.assertIsNone(result, msg=err)
+        policy = self.get_authentication_policy("regularPolicy")
+        self.assertIsNotNone(policy)
+
+        # Raise LdbError when ldb.delete() is called.
+        with patch.object(SamDB, "delete") as delete_mock:
+            delete_mock.side_effect = LdbError("Custom error message")
+            result, out, err = self.runcmd("domain", "auth", "policy", "delete",
+                                           "--name", "regularPolicy")
+            self.assertEqual(result, -1)
+            self.assertIn("ERROR: Custom error message", err)
+
+            # When not using --force we get a hint.
+            self.assertIn("Try --force", err)
+
+    def test_authentication_policy_delete_protected_fails(self):
+        """Test deleting an authentication policy, but it fails."""
+        # Create protected authentication policy.
+        result, out, err = self.runcmd("domain", "auth", "policy", "create",
+                                       "--name=protectedPolicy",
+                                       "--protect")
+        self.assertIsNone(result, msg=err)
+        policy = self.get_authentication_policy("protectedPolicy")
+        self.assertIsNotNone(policy)
+
+        # Raise LdbError when ldb.delete() is called.
+        with patch.object(SamDB, "delete") as delete_mock:
+            delete_mock.side_effect = LdbError("Custom error message")
+            result, out, err = self.runcmd("domain", "auth", "policy", "delete",
+                                           "--name", "protectedPolicy",
+                                           "--force")
+            self.assertEqual(result, -1)
+            self.assertIn("ERROR: Custom error message", err)
+
+            # When using --force we don't get the hint.
+            self.assertNotIn("Try --force", err)
diff --git a/python/samba/tests/samba_tool/domain_auth_silo.py b/python/samba/tests/samba_tool/domain_auth_silo.py
new file mode 100644 (file)
index 0000000..0970559
--- /dev/null
@@ -0,0 +1,420 @@
+# Unix SMB/CIFS implementation.
+#
+# Tests for samba-tool domain auth silo command
+#
+# Copyright (C) Catalyst.Net Ltd. 2023
+#
+# Written by Rob van der Linde <rob@catalyst.net.nz>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+import json
+from unittest.mock import patch
+
+from ldb import LdbError
+from samba.samdb import SamDB
+from samba.sd_utils import SDUtils
+
+from .domain_auth_base import BaseAuthCmdTest
+
+
+class AuthSiloCmdTestCase(BaseAuthCmdTest):
+
+    def test_authentication_silo_list(self):
+        """Test listing authentication silos in list format."""
+        result, out, err = self.runcmd("domain", "auth", "silo", "list")
+        self.assertIsNone(result, msg=err)
+
+        # Check each silo we created is there.
+        for silo in self.silos:
+            self.assertIn(silo, out)
+
+    def test_authentication_silo_list_json(self):
+        """Test listing authentication silos in JSON format."""
+        result, out, err = self.runcmd("domain", "auth", "silo",
+                                       "list", "--json")
+        self.assertIsNone(result, msg=err)
+
+        # we should get valid json
+        silos = json.loads(out)
+
+        # each silo in self.silos must be present
+        for name in self.silos:
+            silo = silos[name]
+            self.assertIn("msDS-AuthNPolicySilo", list(silo["objectClass"]))
+            self.assertIn("description", silo)
+            self.assertIn("msDS-ComputerAuthNPolicy", silo)
+            self.assertIn("msDS-ServiceAuthNPolicy", silo)
+            self.assertIn("msDS-UserAuthNPolicy", silo)
+            self.assertIn("objectGUID", silo)
+
+    def test_authentication_silo_view(self):
+        """Test viewing a single authentication silo."""
+        result, out, err = self.runcmd("domain", "auth", "silo", "view",
+                                       "--name", "Developers")
+        self.assertIsNone(result, msg=err)
+
+        # we should get valid json
+        silo = json.loads(out)
+
+        # check a few fields only
+        self.assertEqual(silo["cn"], "Developers")
+        self.assertEqual(silo["description"], "Developers, Developers")
+
+    def test_authentication_silo_view_notfound(self):
+        """Test viewing an authentication silo that doesn't exist."""
+        result, out, err = self.runcmd("domain", "auth", "silo", "view",
+                                       "--name", "doesNotExist")
+        self.assertEqual(result, -1)
+        self.assertIn("ERROR: Authentication silo doesNotExist not found.", err)
+
+    def test_authentication_silo_view_name_required(self):
+        """Test view authentication silo without --name argument."""
+        result, out, err = self.runcmd("domain", "auth", "silo", "view")
+        self.assertEqual(result, -1)
+        self.assertIn("Argument --name is required.", err)
+
+    def test_authentication_silo_create_single_policy(self):
+        """Test creating a new authentication silo with a single policy."""
+        result, out, err = self.runcmd("domain", "auth", "silo", "create",
+                                       "--name", "singlePolicy",
+                                       "--policy", "Single Policy")
+        self.assertIsNone(result, msg=err)
+
+        # Check silo that was created
+        silo = self.get_authentication_silo("singlePolicy")
+        self.assertEqual(str(silo["cn"]), "singlePolicy")
+        self.assertIn("Single Policy", str(silo["msDS-UserAuthNPolicy"]))
+        self.assertIn("Single Policy", str(silo["msDS-ServiceAuthNPolicy"]))
+        self.assertIn("Single Policy", str(silo["msDS-ComputerAuthNPolicy"]))
+        self.assertEqual(str(silo["msDS-AuthNPolicySiloEnforced"]), "TRUE")
+
+    def test_authentication_silo_create_multiple_policies(self):
+        """Test creating a new authentication silo with multiple policies."""
+        result, out, err = self.runcmd("domain", "auth", "silo", "create",
+                                       "--name", "multiplePolicies",
+                                       "--user-policy", "User Policy",
+                                       "--service-policy", "Service Policy",
+                                       "--computer-policy", "Computer Policy")
+        self.assertIsNone(result, msg=err)
+
+        # Check silo that was created.
+        silo = self.get_authentication_silo("multiplePolicies")
+        self.assertEqual(str(silo["cn"]), "multiplePolicies")
+        self.assertIn("User Policy", str(silo["msDS-UserAuthNPolicy"]))
+        self.assertIn("Service Policy", str(silo["msDS-ServiceAuthNPolicy"]))
+        self.assertIn("Computer Policy", str(silo["msDS-ComputerAuthNPolicy"]))
+        self.assertEqual(str(silo["msDS-AuthNPolicySiloEnforced"]), "TRUE")
+
+    def test_authentication_silo_create_already_exists(self):
+        """Test creating a new authentication silo that already exists."""
+        result, out, err = self.runcmd("domain", "auth", "silo", "create",
+                                       "--name", "Developers",
+                                       "--policy", "Single Policy")
+        self.assertEqual(result, -1)
+        self.assertIn("Authentication silo Developers already exists.", err)
+
+    def test_authentication_silo_create_name_missing(self):
+        """Test create authentication silo without --name argument."""
+        result, out, err = self.runcmd("domain", "auth", "silo", "create",
+                                       "--policy", "Single Policy")
+        self.assertEqual(result, -1)
+        self.assertIn("Argument --name is required.", err)
+
+    def test_authentication_silo_create_audit(self):
+        """Test create authentication silo with --audit flag."""
+        result, out, err = self.runcmd("domain", "auth", "silo", "create",
+                                       "--name", "auditPolicies",
+                                       "--policy", "Single Policy",
+                                       "--audit")
+        self.assertIsNone(result, msg=err)
+
+        # fetch and check silo
+        silo = self.get_authentication_silo("auditPolicies")
+        self.assertEqual(str(silo["msDS-AuthNPolicySiloEnforced"]), "FALSE")
+
+    def test_authentication_silo_create_enforce(self):
+        """Test create authentication silo with --enforce flag."""
+        result, out, err = self.runcmd("domain", "auth", "silo", "create",
+                                       "--name", "enforcePolicies",
+                                       "--policy", "Single Policy",
+                                       "--enforce")
+        self.assertIsNone(result, msg=err)
+
+        # fetch and check silo
+        silo = self.get_authentication_silo("enforcePolicies")
+        self.assertEqual(str(silo["msDS-AuthNPolicySiloEnforced"]), "TRUE")
+
+    def test_authentication_silo_create_audit_enforce_together(self):
+        """Test create authentication silo using both --audit and --enforce."""
+        result, out, err = self.runcmd("domain", "auth", "silo", "create",
+                                       "--name", "enforceTogether",
+                                       "--policy", "Single Policy",
+                                       "--audit", "--enforce")
+        self.assertEqual(result, -1)
+        self.assertIn("--audit and --enforce cannot be used together.", err)
+
+    def test_authentication_silo_create_protect_unprotect_together(self):
+        """Test create authentication silo using --protect and --unprotect."""
+        result, out, err = self.runcmd("domain", "auth", "silo",
+                                       "create", "--name", "protectTogether",
+                                       "--policy", "Single Policy",
+                                       "--protect", "--unprotect")
+        self.assertEqual(result, -1)
+        self.assertIn("--protect and --unprotect cannot be used together.", err)
+
+    def test_authentication_silo_create_policy_notfound(self):
+        """Test create authentication silo with a policy that doesn't exist."""
+        result, out, err = self.runcmd("domain", "auth", "silo", "create",
+                                       "--name", "policyNotFound",
+                                       "--policy", "Invalid Policy")
+        self.assertEqual(result, -1)
+        self.assertIn(f"Authentication policy Invalid Policy not found.", err)
+
+    def test_authentication_silo_create_fails(self):
+        """Test creating an authentication silo, but it fails."""
+        # Raise LdbError when ldb.add() is called.
+        with patch.object(SamDB, "add") as add_mock:
+            add_mock.side_effect = LdbError("Custom error message")
+            result, out, err = self.runcmd("domain", "auth", "silo", "create",
+                                           "--name", "createFails",
+                                           "--policy", "Single Policy")
+            self.assertEqual(result, -1)
+            self.assertIn("ERROR: Custom error message", err)
+
+    def test_authentication_silo_modify_description(self):
+        """Test modify authentication silo changing the description field."""
+        # check original value
+        silo = self.get_authentication_silo("qa")
+        self.assertNotEqual(str(silo["description"]), "Testing Team")
+
+        result, out, err = self.runcmd("domain", "auth", "silo", "modify",
+                                       "--name", "qa",
+                                       "--description", "Testing Team")
+        self.assertIsNone(result, msg=err)
+
+        # check new value
+        silo = self.get_authentication_silo("qa")
+        self.assertEqual(str(silo["description"]), "Testing Team")
+
+    def test_authentication_silo_modify_audit_enforce(self):
+        """Test modify authentication silo setting --audit and --enforce."""
+        result, out, err = self.runcmd("domain", "auth", "silo", "modify",
+                                       "--name", "Developers",
+                                       "--audit")
+        self.assertIsNone(result, msg=err)
+
+        # Check silo is set to audit.
+        silo = self.get_authentication_silo("developers")
+        self.assertEqual(str(silo["msDS-AuthNPolicySiloEnforced"]), "FALSE")
+
+        result, out, err = self.runcmd("domain", "auth", "silo", "modify",
+                                       "--name", "Developers",
+                                       "--enforce")
+        self.assertIsNone(result, msg=err)
+
+        # Check is set to enforce.
+        silo = self.get_authentication_silo("developers")
+        self.assertEqual(str(silo["msDS-AuthNPolicySiloEnforced"]), "TRUE")
+
+    def test_authentication_silo_modify_protect_unprotect(self):
+        """Test modify un-protecting and protecting an authentication silo."""
+        utils = SDUtils(self.samdb)
+        result, out, err = self.runcmd("domain", "auth", "silo", "modify",
+                                       "--name", "managers",
+                                       "--protect")
+        self.assertIsNone(result, msg=err)
+
+        # Check that silo was protected.
+        silo = self.get_authentication_silo("managers")
+        desc = utils.get_sd_as_sddl(silo["dn"])
+        self.assertIn("(D;;DTSD;;;WD)", desc)
+
+        result, out, err = self.runcmd("domain", "auth", "silo", "modify",
+                                       "--name", "managers",
+                                       "--unprotect")
+        self.assertIsNone(result, msg=err)
+
+        # Check that silo was unprotected.
+        silo = self.get_authentication_silo("managers")
+        desc = utils.get_sd_as_sddl(silo["dn"])
+        self.assertNotIn("(D;;DTSD;;;WD)", desc)
+
+    def test_authentication_silo_modify_audit_enforce_together(self):
+        """Test modify silo doesn't allow both --audit and --enforce."""
+        result, out, err = self.runcmd("domain", "auth", "silo", "modify",
+                                       "--name", "qa",
+                                       "--audit", "--enforce")
+        self.assertEqual(result, -1)
+        self.assertIn("--audit and --enforce cannot be used together.", err)
+
+    def test_authentication_silo_modify_protect_unprotect_together(self):
+        """Test modify silo using both --protect and --unprotect."""
+        result, out, err = self.runcmd("domain", "auth", "silo", "modify",
+                                       "--name", "developers",
+                                       "--protect", "--unprotect")
+        self.assertEqual(result, -1)
+        self.assertIn("--protect and --unprotect cannot be used together.", err)
+
+    def test_authentication_silo_modify_notfound(self):
+        """Test modify an authentication silo that doesn't exist."""
+        result, out, err = self.runcmd("domain", "auth", "silo", "modify",
+                                       "--name", "doesNotExist",
+                                       "--description=NewDescription")
+        self.assertEqual(result, -1)
+        self.assertIn("ERROR: Authentication silo doesNotExist not found.", err)
+
+    def test_authentication_silo_modify_name_missing(self):
+        """Test modify authentication silo without --name argument."""
+        result, out, err = self.runcmd("domain", "auth", "silo", "modify")
+        self.assertEqual(result, -1)
+        self.assertIn("Argument --name is required.", err)
+
+    def test_authentication_silo_modify_fails(self):
+        """Test modify authentication silo, but it fails."""
+        # Raise LdbError when ldb.modify() is called.
+        with patch.object(SamDB, "modify") as add_mock:
+            add_mock.side_effect = LdbError("Custom error message")
+            result, out, err = self.runcmd("domain", "auth", "silo", "modify",
+                                           "--name", "developers",
+                                           "--description", "Devs")
+            self.assertEqual(result, -1)
+            self.assertIn("ERROR: Custom error message", err)
+
+    def test_authentication_silo_delete(self):
+        """Test deleting an authentication silo that is not protected."""
+        # Create non-protected authentication silo.
+        result, out, err = self.runcmd("domain", "auth", "silo", "create",
+                                       "--name=deleteTest",
+                                       "--policy", "User Policy")
+        self.assertIsNone(result, msg=err)
+        silo = self.get_authentication_silo("deleteTest")
+        self.assertIsNotNone(silo)
+
+        # Do the deletion.
+        result, out, err = self.runcmd("domain", "auth", "silo", "delete",
+                                       "--name", "deleteTest")
+        self.assertIsNone(result, msg=err)
+
+        # Authentication silo shouldn't exist anymore.
+        silo = self.get_authentication_silo("deleteTest")
+        self.assertIsNone(silo)
+
+    def test_authentication_silo_delete_protected(self):
+        """Test deleting a protected auth silo, with and without --force."""
+        # Create protected authentication silo.
+        result, out, err = self.runcmd("domain", "auth", "silo", "create",
+                                       "--name=deleteProtected",
+                                       "--policy", "User Policy",
+                                       "--protect")
+        self.assertIsNone(result, msg=err)
+        silo = self.get_authentication_silo("deleteProtected")
+        self.assertIsNotNone(silo)
+
+        # Do the deletion.
+        result, out, err = self.runcmd("domain", "auth", "silo", "delete",
+                                       "--name=deleteProtected")
+        self.assertEqual(result, -1)
+
+        # Authentication silo should still exist.
+        silo = self.get_authentication_silo("deleteProtected")
+        self.assertIsNotNone(silo)
+
+        # Try a force delete instead.
+        result, out, err = self.runcmd("domain", "auth", "silo", "delete",
+                                       "--name=deleteProtected", "--force")
+        self.assertIsNone(result, msg=err)
+
+        # Authentication silo shouldn't exist anymore.
+        silo = self.get_authentication_silo("deleteProtected")
+        self.assertIsNone(silo)
+
+    def test_authentication_silo_delete_notfound(self):
+        """Test deleting an authentication silo that doesn't exist."""
+        result, out, err = self.runcmd("domain", "auth", "silo", "delete",
+                                       "--name", "doesNotExist")
+        self.assertEqual(result, -1)
+        self.assertIn("Authentication silo doesNotExist not found.", err)
+
+    def test_authentication_silo_delete_name_required(self):
+        """Test deleting an authentication silo without --name argument."""
+        result, out, err = self.runcmd("domain", "auth", "silo", "delete")
+        self.assertEqual(result, -1)
+        self.assertIn("Argument --name is required.", err)
+
+    def test_authentication_silo_delete_force_fails(self):
+        """Test deleting an authentication silo with --force, but it fails."""
+        # Create protected authentication silo.
+        result, out, err = self.runcmd("domain", "auth", "silo", "create",
+                                       "--name=deleteForceFail",
+                                       "--policy", "User Policy",
+                                       "--protect")
+        self.assertIsNone(result, msg=err)
+        silo = self.get_authentication_silo("deleteForceFail")
+        self.assertIsNotNone(silo)
+
+        # Try delete with --force.
+        # Patch SDUtils.dacl_delete_aces with a Mock that raises LdbError.
+        with patch.object(SDUtils, "dacl_delete_aces") as delete_mock:
+            delete_mock.side_effect = LdbError("Custom error message")
+            result, out, err = self.runcmd("domain", "auth", "silo", "delete",
+                                           "--name", "deleteForceFail",
+                                           "--force")
+            self.assertEqual(result, -1)
+            self.assertIn("ERROR: Custom error message", err)
+
+    def test_authentication_silo_delete_fails(self):
+        """Test deleting an authentication silo, but it fails."""
+        # Create regular authentication silo.
+        result, out, err = self.runcmd("domain", "auth", "silo", "create",
+                                       "--name=regularSilo",
+                                       "--policy", "User Policy")
+        self.assertIsNone(result, msg=err)
+        silo = self.get_authentication_silo("regularSilo")
+        self.assertIsNotNone(silo)
+
+        # Raise LdbError when ldb.delete() is called.
+        with patch.object(SamDB, "delete") as delete_mock:
+            delete_mock.side_effect = LdbError("Custom error message")
+            result, out, err = self.runcmd("domain", "auth", "silo", "delete",
+                                           "--name", "regularSilo")
+            self.assertEqual(result, -1)
+            self.assertIn("ERROR: Custom error message", err)
+
+            # When not using --force we get a hint.
+            self.assertIn("Try --force", err)
+
+    def test_authentication_silo_delete_protected_fails(self):
+        """Test deleting an authentication silo, but it fails."""
+        # Create protected authentication silo.
+        result, out, err = self.runcmd("domain", "auth", "silo", "create",
+                                       "--name=protectedSilo",
+                                       "--policy", "User Policy",
+                                       "--protect")
+        self.assertIsNone(result, msg=err)
+        silo = self.get_authentication_silo("protectedSilo")
+        self.assertIsNotNone(silo)
+
+        # Raise LdbError when ldb.delete() is called.
+        with patch.object(SamDB, "delete") as delete_mock:
+            delete_mock.side_effect = LdbError("Custom error message")
+            result, out, err = self.runcmd("domain", "auth", "silo", "delete",
+                                           "--name", "protectedSilo",
+                                           "--force")
+            self.assertEqual(result, -1)
+            self.assertIn("ERROR: Custom error message", err)
+
+            # When using --force we don't get the hint.
+            self.assertNotIn("Try --force", err)
diff --git a/selftest/knownfail.d/silo-client-tool b/selftest/knownfail.d/silo-client-tool
new file mode 100644 (file)
index 0000000..4eca270
--- /dev/null
@@ -0,0 +1,2 @@
+^samba.tests.samba_tool.domain_auth_silo.samba.tests.samba_tool.domain_auth_silo.AuthSiloCmdTestCase.test_authentication_silo_delete_protected\(.*\)
+^samba.tests.samba_tool.domain_auth_policy.samba.tests.samba_tool.domain_auth_policy.AuthPolicyCmdTestCase.test_authentication_policy_delete_protected\(.*\)
index 235d87266fd7830cdb38b275d1c4018142ced19d..cc46effdf91c5b304aaf103c71e8530b099460a3 100755 (executable)
@@ -1141,6 +1141,8 @@ planpythontestsuite("ad_dc_default:local", "samba.tests.samba_tool.contact")
 planpythontestsuite("ad_dc_default:local", "samba.tests.samba_tool.forest")
 planpythontestsuite("ad_dc_default:local", "samba.tests.samba_tool.schema")
 planpythontestsuite("ad_dc_default", "samba.tests.samba_tool.claim")
+planpythontestsuite("ad_dc_default", "samba.tests.samba_tool.domain_auth_policy")
+planpythontestsuite("ad_dc_default", "samba.tests.samba_tool.domain_auth_silo")
 planpythontestsuite("schema_dc:local", "samba.tests.samba_tool.schema")
 planpythontestsuite("ad_dc:local", "samba.tests.samba_tool.ntacl")
 planpythontestsuite("none", "samba.tests.samba_tool.provision_password_check")