summaryrefslogtreecommitdiffstats
path: root/python/samba/tests/samba_tool/domain_auth_policy.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/samba/tests/samba_tool/domain_auth_policy.py')
-rw-r--r--python/samba/tests/samba_tool/domain_auth_policy.py1517
1 files changed, 1517 insertions, 0 deletions
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
index 0000000..1854037
--- /dev/null
+++ b/python/samba/tests/samba_tool/domain_auth_policy.py
@@ -0,0 +1,1517 @@
+# 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 samba.dcerpc import security
+from samba.ndr import ndr_pack, ndr_unpack
+from samba.netcmd.domain.models.exceptions import ModelError
+from samba.samdb import SamDB
+from samba.sd_utils import SDUtils
+
+from .silo_base import SiloTest
+
+
+class AuthPolicyCmdTestCase(SiloTest):
+
+ def test_list(self):
+ """Test listing authentication policies in list format."""
+ result, out, err = self.runcmd("domain", "auth", "policy", "list")
+ self.assertIsNone(result, msg=err)
+
+ expected_policies = ["User Policy", "Service Policy", "Computer Policy"]
+
+ for policy in expected_policies:
+ self.assertIn(policy, out)
+
+ def test_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)
+
+ expected_policies = ["User Policy", "Service Policy", "Computer Policy"]
+
+ for name in expected_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_view(self):
+ """Test viewing a single authentication policy."""
+ result, out, err = self.runcmd("domain", "auth", "policy", "view",
+ "--name", "User Policy")
+ self.assertIsNone(result, msg=err)
+
+ # we should get valid json
+ policy = json.loads(out)
+
+ # check a few fields only
+ self.assertEqual(policy["cn"], "User Policy")
+ self.assertEqual(policy["msDS-AuthNPolicyEnforced"], True)
+
+ def test_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_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_create__success(self):
+ """Test creating a new authentication policy."""
+ name = self.unique_name()
+
+ self.addCleanup(self.delete_authentication_policy, name=name, force=True)
+ result, out, err = self.runcmd("domain", "auth", "policy", "create",
+ "--name", name)
+ self.assertIsNone(result, msg=err)
+
+ # Check policy that was created
+ policy = self.get_authentication_policy(name)
+ self.assertEqual(str(policy["cn"]), name)
+ self.assertEqual(str(policy["msDS-AuthNPolicyEnforced"]), "TRUE")
+
+ def test_create__description(self):
+ """Test creating a new authentication policy with description set."""
+ name = self.unique_name()
+
+ self.addCleanup(self.delete_authentication_policy, name=name, force=True)
+ result, out, err = self.runcmd("domain", "auth", "policy", "create",
+ "--name", name,
+ "--description", "Custom Description")
+ self.assertIsNone(result, msg=err)
+
+ # Check policy description
+ policy = self.get_authentication_policy(name)
+ self.assertEqual(str(policy["cn"]), name)
+ self.assertEqual(str(policy["description"]), "Custom Description")
+
+ def test_create__user_tgt_lifetime_mins(self):
+ """Test create a new authentication policy with --user-tgt-lifetime-mins.
+
+ Also checks the upper and lower bounds are handled.
+ """
+ name = self.unique_name()
+
+ self.addCleanup(self.delete_authentication_policy, name=name, force=True)
+ result, out, err = self.runcmd("domain", "auth", "policy", "create",
+ "--name", name,
+ "--user-tgt-lifetime-mins", "60")
+ self.assertIsNone(result, msg=err)
+
+ # Check policy fields.
+ policy = self.get_authentication_policy(name)
+ self.assertEqual(str(policy["cn"]), name)
+ self.assertEqual(str(policy["msDS-UserTGTLifetime"]), "60")
+
+ # check lower bounds (45)
+ result, out, err = self.runcmd("domain", "auth", "policy", "create",
+ "--name", name + "Lower",
+ "--user-tgt-lifetime-mins", "44")
+ self.assertEqual(result, -1)
+ self.assertIn("--user-tgt-lifetime-mins must be between 45 and 2147483647",
+ err)
+
+ # check upper bounds (2147483647)
+ result, out, err = self.runcmd("domain", "auth", "policy", "create",
+ "--name", name + "Upper",
+ "--user-tgt-lifetime-mins", "2147483648")
+ self.assertEqual(result, -1)
+ self.assertIn("--user-tgt-lifetime-mins must be between 45 and 2147483647",
+ err)
+
+ def test_create__user_allowed_to_authenticate_from_device_group(self):
+ """Tests the --user-allowed-to-authenticate-from-device-group shortcut."""
+ name = self.unique_name()
+ expected = "O:SYG:SYD:(XA;OICI;CR;;;WD;(Member_of_any {SID(%s)}))" % (
+ self.device_group.object_sid)
+
+ self.addCleanup(self.delete_authentication_policy, name=name, force=True)
+ result, out, err = self.runcmd("domain", "auth", "policy", "create",
+ "--name", name,
+ "--user-allowed-to-authenticate-from-device-group",
+ self.device_group.name)
+ self.assertIsNone(result, msg=err)
+
+ # Check policy fields.
+ policy = self.get_authentication_policy(name)
+ self.assertEqual(str(policy["cn"]), name)
+
+ # Check generated SDDL.
+ desc = policy["msDS-UserAllowedToAuthenticateFrom"][0]
+ sddl = ndr_unpack(security.descriptor, desc).as_sddl()
+ self.assertEqual(sddl, expected)
+
+ def test_create__user_allowed_to_authenticate_from_device_silo(self):
+ """Tests the --user-allowed-to-authenticate-from-device-silo shortcut."""
+ name = self.unique_name()
+
+ self.addCleanup(self.delete_authentication_policy, name=name, force=True)
+ result, out, err = self.runcmd("domain", "auth", "policy", "create",
+ "--name", name,
+ "--user-allowed-to-authenticate-from-device-silo",
+ "Developers")
+ self.assertIsNone(result, msg=err)
+
+ # Check policy fields.
+ policy = self.get_authentication_policy(name)
+ self.assertEqual(str(policy["cn"]), name)
+
+ # Check generated SDDL.
+ desc = policy["msDS-UserAllowedToAuthenticateFrom"][0]
+ sddl = ndr_unpack(security.descriptor, desc).as_sddl()
+ self.assertEqual(
+ sddl,
+ 'O:SYG:SYD:(XA;OICI;CR;;;WD;(@USER.ad://ext/AuthenticationSilo == "Developers"))')
+
+ def test_create__user_allowed_to_authenticate_to_by_group(self):
+ """Tests the --user-allowed-to-authenticate-to-by-group shortcut."""
+ name = self.unique_name()
+ expected = "O:SYG:SYD:(XA;OICI;CR;;;WD;(Member_of_any {SID(%s)}))" % (
+ self.device_group.object_sid)
+
+ # Create a user with authenticate to by group attribute.
+ self.addCleanup(self.delete_authentication_policy, name=name, force=True)
+ result, out, err = self.runcmd(
+ "domain", "auth", "policy", "create", "--name", name,
+ "--user-allowed-to-authenticate-to-by-group",
+ self.device_group.name)
+ self.assertIsNone(result, msg=err)
+
+ # Check user allowed to authenticate to field was modified.
+ policy = self.get_authentication_policy(name)
+ self.assertEqual(str(policy["cn"]), name)
+ desc = policy["msDS-UserAllowedToAuthenticateTo"][0]
+ sddl = ndr_unpack(security.descriptor, desc).as_sddl()
+ self.assertEqual(sddl, expected)
+
+ def test_create__user_allowed_to_authenticate_to_by_silo(self):
+ """Tests the --user-allowed-to-authenticate-to-by-silo shortcut."""
+ name = self.unique_name()
+ expected = ('O:SYG:SYD:(XA;OICI;CR;;;WD;(@USER.ad://ext/'
+ 'AuthenticationSilo == "QA"))')
+
+ # Create a user with authenticate to by silo attribute.
+ self.addCleanup(self.delete_authentication_policy, name=name, force=True)
+ result, out, err = self.runcmd(
+ "domain", "auth", "policy", "create", "--name", name,
+ "--user-allowed-to-authenticate-to-by-silo", "QA")
+ self.assertIsNone(result, msg=err)
+
+ # Check user allowed to authenticate to field was modified.
+ policy = self.get_authentication_policy(name)
+ self.assertEqual(str(policy["cn"]), name)
+ desc = policy["msDS-UserAllowedToAuthenticateTo"][0]
+ sddl = ndr_unpack(security.descriptor, desc).as_sddl()
+ self.assertEqual(sddl, expected)
+
+ def test_create__service_tgt_lifetime_mins(self):
+ """Test create a new authentication policy with --service-tgt-lifetime-mins.
+
+ Also checks the upper and lower bounds are handled.
+ """
+ name = self.unique_name()
+
+ self.addCleanup(self.delete_authentication_policy, name=name, force=True)
+ result, out, err = self.runcmd("domain", "auth", "policy", "create",
+ "--name", name,
+ "--service-tgt-lifetime-mins", "60")
+ self.assertIsNone(result, msg=err)
+
+ # Check policy fields.
+ policy = self.get_authentication_policy(name)
+ self.assertEqual(str(policy["cn"]), name)
+ self.assertEqual(str(policy["msDS-ServiceTGTLifetime"]), "60")
+
+ # check lower bounds (45)
+ result, out, err = self.runcmd("domain", "auth", "policy", "create",
+ "--name", name,
+ "--service-tgt-lifetime-mins", "44")
+ self.assertEqual(result, -1)
+ self.assertIn("--service-tgt-lifetime-mins must be between 45 and 2147483647",
+ err)
+
+ # check upper bounds (2147483647)
+ result, out, err = self.runcmd("domain", "auth", "policy", "create",
+ "--name", name,
+ "--service-tgt-lifetime-mins", "2147483648")
+ self.assertEqual(result, -1)
+ self.assertIn("--service-tgt-lifetime-mins must be between 45 and 2147483647",
+ err)
+
+ def test_create__service_allowed_to_authenticate_from_device_group(self):
+ """Tests the --service-allowed-to-authenticate-from-device-group shortcut."""
+ name = self.unique_name()
+ expected = "O:SYG:SYD:(XA;OICI;CR;;;WD;(Member_of_any {SID(%s)}))" % (
+ self.device_group.object_sid)
+
+ self.addCleanup(self.delete_authentication_policy, name=name, force=True)
+ result, out, err = self.runcmd("domain", "auth", "policy", "create",
+ "--name", name,
+ "--service-allowed-to-authenticate-from-device-group",
+ self.device_group.name)
+ self.assertIsNone(result, msg=err)
+
+ # Check policy fields.
+ policy = self.get_authentication_policy(name)
+ self.assertEqual(str(policy["cn"]), name)
+
+ # Check generated SDDL.
+ desc = policy["msDS-ServiceAllowedToAuthenticateFrom"][0]
+ sddl = ndr_unpack(security.descriptor, desc).as_sddl()
+ self.assertEqual(sddl, expected)
+
+ def test_create__service_allowed_to_authenticate_from_device_silo(self):
+ """Tests the --service-allowed-to-authenticate-from-device-silo shortcut."""
+ name = self.unique_name()
+
+ self.addCleanup(self.delete_authentication_policy, name=name, force=True)
+ result, out, err = self.runcmd("domain", "auth", "policy", "create",
+ "--name", name,
+ "--service-allowed-to-authenticate-from-device-silo",
+ "Managers")
+ self.assertIsNone(result, msg=err)
+
+ # Check policy fields.
+ policy = self.get_authentication_policy(name)
+ self.assertEqual(str(policy["cn"]), name)
+ desc = policy["msDS-ServiceAllowedToAuthenticateFrom"][0]
+
+ # Check generated SDDL.
+ sddl = ndr_unpack(security.descriptor, desc).as_sddl()
+ self.assertEqual(
+ sddl,
+ 'O:SYG:SYD:(XA;OICI;CR;;;WD;(@USER.ad://ext/AuthenticationSilo == "Managers"))')
+
+ def test_create__service_allowed_to_authenticate_to_by_group(self):
+ """Tests the --service-allowed-to-authenticate-to-by-group shortcut."""
+ name = self.unique_name()
+ expected = "O:SYG:SYD:(XA;OICI;CR;;;WD;(Member_of_any {SID(%s)}))" % (
+ self.device_group.object_sid)
+
+ # Create a user with authenticate to by group attribute.
+ self.addCleanup(self.delete_authentication_policy, name=name, force=True)
+ result, out, err = self.runcmd(
+ "domain", "auth", "policy", "create", "--name", name,
+ "--service-allowed-to-authenticate-to-by-group",
+ self.device_group.name)
+ self.assertIsNone(result, msg=err)
+
+ # Check user allowed to authenticate to field was modified.
+ policy = self.get_authentication_policy(name)
+ self.assertEqual(str(policy["cn"]), name)
+ desc = policy["msDS-ServiceAllowedToAuthenticateTo"][0]
+ sddl = ndr_unpack(security.descriptor, desc).as_sddl()
+ self.assertEqual(sddl, expected)
+
+ def test_create__service_allowed_to_authenticate_to_by_silo(self):
+ """Tests the --service-allowed-to-authenticate-to-by-silo shortcut."""
+ name = self.unique_name()
+ expected = ('O:SYG:SYD:(XA;OICI;CR;;;WD;(@USER.ad://ext/'
+ 'AuthenticationSilo == "Managers"))')
+
+ # Create a user with authenticate to by silo attribute.
+ self.addCleanup(self.delete_authentication_policy, name=name, force=True)
+ result, out, err = self.runcmd(
+ "domain", "auth", "policy", "create", "--name", name,
+ "--service-allowed-to-authenticate-to-by-silo", "Managers")
+ self.assertIsNone(result, msg=err)
+
+ # Check user allowed to authenticate to field was modified.
+ policy = self.get_authentication_policy(name)
+ self.assertEqual(str(policy["cn"]), name)
+ desc = policy["msDS-ServiceAllowedToAuthenticateTo"][0]
+ sddl = ndr_unpack(security.descriptor, desc).as_sddl()
+ self.assertEqual(sddl, expected)
+
+ def test_create__computer_tgt_lifetime_mins(self):
+ """Test create a new authentication policy with --computer-tgt-lifetime-mins.
+
+ Also checks the upper and lower bounds are handled.
+ """
+ name = self.unique_name()
+
+ self.addCleanup(self.delete_authentication_policy, name=name, force=True)
+ result, out, err = self.runcmd("domain", "auth", "policy", "create",
+ "--name", name,
+ "--computer-tgt-lifetime-mins", "60")
+ self.assertIsNone(result, msg=err)
+
+ # Check policy fields.
+ policy = self.get_authentication_policy(name)
+ self.assertEqual(str(policy["cn"]), name)
+ self.assertEqual(str(policy["msDS-ComputerTGTLifetime"]), "60")
+
+ # check lower bounds (45)
+ result, out, err = self.runcmd("domain", "auth", "policy", "create",
+ "--name", name + "Lower",
+ "--computer-tgt-lifetime-mins", "44")
+ self.assertEqual(result, -1)
+ self.assertIn("--computer-tgt-lifetime-mins must be between 45 and 2147483647",
+ err)
+
+ # check upper bounds (2147483647)
+ result, out, err = self.runcmd("domain", "auth", "policy", "create",
+ "--name", name + "Upper",
+ "--computer-tgt-lifetime-mins", "2147483648")
+ self.assertEqual(result, -1)
+ self.assertIn("--computer-tgt-lifetime-mins must be between 45 and 2147483647",
+ err)
+
+ def test_create__computer_allowed_to_authenticate_to_by_group(self):
+ """Tests the --computer-allowed-to-authenticate-to-by-group shortcut."""
+ name = self.unique_name()
+ expected = "O:SYG:SYD:(XA;OICI;CR;;;WD;(Member_of_any {SID(%s)}))" % (
+ self.device_group.object_sid)
+
+ # Create a user with authenticate to by group attribute.
+ self.addCleanup(self.delete_authentication_policy, name=name, force=True)
+ result, out, err = self.runcmd(
+ "domain", "auth", "policy", "create", "--name", name,
+ "--computer-allowed-to-authenticate-to-by-group",
+ self.device_group.name)
+ self.assertIsNone(result, msg=err)
+
+ # Check user allowed to authenticate to field was modified.
+ policy = self.get_authentication_policy(name)
+ self.assertEqual(str(policy["cn"]), name)
+ desc = policy["msDS-ComputerAllowedToAuthenticateTo"][0]
+ sddl = ndr_unpack(security.descriptor, desc).as_sddl()
+ self.assertEqual(sddl, expected)
+
+ def test_create__computer_allowed_to_authenticate_to_by_silo(self):
+ """Tests the --computer-allowed-to-authenticate-to-by-silo shortcut."""
+ name = self.unique_name()
+ expected = ('O:SYG:SYD:(XA;OICI;CR;;;WD;(@USER.ad://ext/'
+ 'AuthenticationSilo == "QA"))')
+
+ # Create a user with authenticate to by silo attribute.
+ self.addCleanup(self.delete_authentication_policy, name=name, force=True)
+ result, out, err = self.runcmd(
+ "domain", "auth", "policy", "create", "--name", name,
+ "--computer-allowed-to-authenticate-to-by-silo", "QA")
+ self.assertIsNone(result, msg=err)
+
+ # Check user allowed to authenticate to field was modified.
+ policy = self.get_authentication_policy(name)
+ self.assertEqual(str(policy["cn"]), name)
+ desc = policy["msDS-ComputerAllowedToAuthenticateTo"][0]
+ sddl = ndr_unpack(security.descriptor, desc).as_sddl()
+ self.assertEqual(sddl, expected)
+
+ def test_create__valid_sddl(self):
+ """Test creating a new authentication policy with valid SDDL in a field."""
+ name = self.unique_name()
+ expected = "O:SYG:SYD:(XA;OICI;CR;;;WD;(Member_of {SID(AO)}))"
+
+ self.addCleanup(self.delete_authentication_policy, name=name, force=True)
+ result, out, err = self.runcmd("domain", "auth", "policy", "create",
+ "--name", name,
+ "--user-allowed-to-authenticate-from",
+ expected)
+ self.assertIsNone(result, msg=err)
+
+ # Check policy fields.
+ policy = self.get_authentication_policy(name)
+ self.assertEqual(str(policy["cn"]), name)
+ desc = policy["msDS-UserAllowedToAuthenticateFrom"][0]
+ sddl = ndr_unpack(security.descriptor, desc).as_sddl()
+ self.assertEqual(sddl, expected)
+
+ def test_create__invalid_sddl(self):
+ """Test creating a new authentication policy with invalid SDDL in a field."""
+ name = self.unique_name()
+
+ result, out, err = self.runcmd("domain", "auth", "policy", "create",
+ "--name", name,
+ "--user-allowed-to-authenticate-from",
+ "*INVALID SDDL*")
+
+ self.assertEqual(result, -1)
+ self.assertIn("Unable to parse SDDL", err)
+ self.assertIn(" *INVALID SDDL*\n ^\n expected '[OGDS]:' section start ", err)
+
+ def test_create__invalid_sddl_conditional_ace(self):
+ """Test creating a new authentication policy with invalid SDDL in a field."""
+ sddl = "O:SYG:SYD:(XA;OICI;CR;;;WD;(Member_of {secret club}))"
+ result, out, err = self.runcmd("domain", "auth", "policy", "create",
+ "--name", "invalidSDDLPolicy2",
+ "--user-allowed-to-authenticate-from",
+ sddl)
+ self.assertEqual(result, -1)
+ self.assertIn("Unable to parse SDDL", err)
+ self.assertIn(sddl, err)
+ self.assertIn(f"\n{'^':>41}", err)
+ self.assertIn("unexpected byte 0x73 's' parsing literal", err)
+ self.assertNotIn(" File ", err)
+
+ def test_create__invalid_sddl_conditional_ace_non_ascii(self):
+ """Test creating a new authentication policy with invalid SDDL in a field."""
+ sddl = 'O:SYG:SYD:(XA;OICI;CR;;;WD;(@User.āāēē == "łē¶ŧ¹⅓þōīŋ“đ¢ð»" && Member_of {secret club}))'
+ result, out, err = self.runcmd("domain", "auth", "policy", "create",
+ "--name", "invalidSDDLPolicy2",
+ "--user-allowed-to-authenticate-from",
+ sddl)
+ self.assertEqual(result, -1)
+ self.assertIn("Unable to parse SDDL", err)
+ self.assertIn(sddl, err)
+ self.assertIn(f"\n{'^':>76}\n", err)
+ self.assertIn(" unexpected byte 0x73 's' parsing literal", err)
+ self.assertNotIn(" File ", err)
+
+ def test_create__invalid_sddl_normal_ace(self):
+ """Test creating a new authentication policy with invalid SDDL in a field."""
+ sddl = "O:SYG:SYD:(A;;;;ZZ)(XA;OICI;CR;;;WD;(Member_of {WD}))"
+ result, out, err = self.runcmd("domain", "auth", "policy", "create",
+ "--name", "invalidSDDLPolicy3",
+ "--user-allowed-to-authenticate-from",
+ sddl)
+ self.assertEqual(result, -1)
+ self.assertIn("Unable to parse SDDL", err)
+ self.assertIn(sddl, err)
+ self.assertIn(f"\n{'^':>13}", err)
+ self.assertIn("\n malformed ACE with only 4 ';'\n", err)
+ self.assertNotIn(" File ", err) # traceback marker
+
+ def test_create__device_attribute_in_sddl_allowed_to(self):
+ """Test creating a new authentication policy that uses
+ user-allowed-to-authenticate-to with a device attribute."""
+
+ sddl = 'O:SYG:SYD:(XA;OICI;CR;;;WD;(@Device.claim == "foo"))'
+
+ name = self.unique_name()
+ self.addCleanup(self.delete_authentication_policy, name=name)
+ result, _, err = self.runcmd("domain", "auth", "policy", "create",
+ "--name", name,
+ "--user-allowed-to-authenticate-to",
+ sddl)
+ self.assertIsNone(result, msg=err)
+
+ def test_create__device_operator_in_sddl_allowed_to(self):
+ """Test creating a new authentication policy that uses
+ user-allowed-to-authenticate-to with a device operator."""
+
+ sddl = 'O:SYG:SYD:(XA;OICI;CR;;;WD;(Not_Device_Member_of {SID(WD)}))'
+
+ name = self.unique_name()
+ self.addCleanup(self.delete_authentication_policy, name=name)
+ result, _, err = self.runcmd("domain", "auth", "policy", "create",
+ "--name", name,
+ "--user-allowed-to-authenticate-to",
+ sddl)
+ self.assertIsNone(result, msg=err)
+
+ def test_create__device_attribute_in_sddl_allowed_from(self):
+ """Test creating a new authentication policy that uses
+ user-allowed-to-authenticate-from with a device attribute."""
+
+ sddl = 'O:SYG:SYD:(XA;OICI;CR;;;WD;(@Device.claim == "foo"))'
+
+ name = self.unique_name()
+ result, _, err = self.runcmd("domain", "auth", "policy", "create",
+ "--name", name,
+ "--user-allowed-to-authenticate-from",
+ sddl)
+ self.assertEqual(result, -1)
+ self.assertIn("Unable to parse SDDL", err)
+ self.assertIn(sddl, err)
+ self.assertIn(f"\n{'^':>31}\n", err)
+ self.assertIn(" a device attribute is not applicable in this context "
+ "(did you intend a user attribute?)",
+ err)
+ self.assertNotIn(" File ", err)
+
+ def test_create__device_operator_in_sddl_allowed_from(self):
+ """Test creating a new authentication policy that uses
+ user-allowed-to-authenticate-from with a device operator."""
+
+ sddl = 'O:SYG:SYD:(XA;OICI;CR;;;WD;(Not_Device_Member_of {SID(WD)}))'
+
+ name = self.unique_name()
+ result, _, err = self.runcmd("domain", "auth", "policy", "create",
+ "--name", name,
+ "--user-allowed-to-authenticate-from",
+ sddl)
+ self.assertEqual(result, -1)
+ self.assertIn("Unable to parse SDDL", err)
+ self.assertIn(sddl, err)
+ self.assertIn(f"\n{'^':>30}\n", err)
+ self.assertIn(" a device‐relative expression will never evaluate to "
+ "true in this context (did you intend a user‐relative "
+ "expression?)",
+ err)
+ self.assertNotIn(" File ", err)
+
+ def test_create__device_attribute_in_sddl_already_exists(self):
+ """Test modifying an existing authentication policy that uses
+ user-allowed-to-authenticate-from with a device attribute."""
+
+ # The SDDL refers to ‘Device.claim’.
+ sddl = 'O:SYG:SYD:(XA;OICI;CR;;;WD;(@Device.claim == "foo"))'
+ domain_sid = security.dom_sid(self.samdb.get_domain_sid())
+ descriptor = security.descriptor.from_sddl(sddl, domain_sid)
+
+ # Manually create an authentication policy that refers to a device
+ # attribute.
+
+ name = self.unique_name()
+ dn = self.get_authn_policies_dn()
+ dn.add_child(f"CN={name}")
+ message = {
+ 'dn': dn,
+ 'msDS-AuthNPolicyEnforced': b'TRUE',
+ 'objectClass': b'msDS-AuthNPolicy',
+ 'msDS-UserAllowedToAuthenticateFrom': ndr_pack(descriptor),
+ }
+
+ self.addCleanup(self.delete_authentication_policy, name=name)
+ self.samdb.add(message)
+
+ # Change the policy description. This should succeed, in spite of the
+ # policy’s referring to a device attribute when it shouldn’t.
+ result, _, err = self.runcmd("domain", "auth", "policy", "modify",
+ "--name", name,
+ "--description", "NewDescription")
+ self.assertIsNone(result, msg=err)
+
+ def test_create__already_exists(self):
+ """Test creating a new authentication policy that already exists."""
+ result, out, err = self.runcmd("domain", "auth", "policy", "create",
+ "--name", "User Policy")
+ self.assertEqual(result, -1)
+ self.assertIn("Authentication policy User Policy already exists", err)
+
+ def test_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_create__audit(self):
+ """Test create authentication policy with --audit flag."""
+ name = self.unique_name()
+
+ self.addCleanup(self.delete_authentication_policy, name=name, force=True)
+ result, out, err = self.runcmd("domain", "auth", "policy", "create",
+ "--name", name,
+ "--audit")
+ self.assertIsNone(result, msg=err)
+
+ # fetch and check policy
+ policy = self.get_authentication_policy(name)
+ self.assertEqual(str(policy["msDS-AuthNPolicyEnforced"]), "FALSE")
+
+ def test_create__enforce(self):
+ """Test create authentication policy with --enforce flag."""
+ name = self.unique_name()
+
+ self.addCleanup(self.delete_authentication_policy, name=name, force=True)
+ result, out, err = self.runcmd("domain", "auth", "policy", "create",
+ "--name", name,
+ "--enforce")
+ self.assertIsNone(result, msg=err)
+
+ # fetch and check policy
+ policy = self.get_authentication_policy(name)
+ self.assertEqual(str(policy["msDS-AuthNPolicyEnforced"]), "TRUE")
+
+ def test_create__audit_enforce_together(self):
+ """Test create auth policy using both --audit and --enforce."""
+ name = self.unique_name()
+
+ result, out, err = self.runcmd("domain", "auth", "policy", "create",
+ "--name", name,
+ "--audit", "--enforce")
+
+ self.assertEqual(result, -1)
+ self.assertIn("--audit and --enforce cannot be used together.", err)
+
+ def test_create__protect_unprotect_together(self):
+ """Test create authentication policy using --protect and --unprotect."""
+ name = self.unique_name()
+
+ result, out, err = self.runcmd("domain", "auth", "policy", "create",
+ "--name", name,
+ "--protect", "--unprotect")
+
+ self.assertEqual(result, -1)
+ self.assertIn("--protect and --unprotect cannot be used together.", err)
+
+ def test_create__user_allowed_to_authenticate_from_repeated(self):
+ """Test repeating similar arguments doesn't make sense to use together.
+
+ --user-allowed-to-authenticate-from
+ --user-allowed-to-authenticate-from-device-silo
+ """
+ sddl = 'O:SYG:SYD:(XA;OICI;CR;;;WD;(@USER.ad://ext/AuthenticationSilo == "Developers"))'
+ name = self.unique_name()
+
+ result, out, err = self.runcmd("domain", "auth", "policy", "create",
+ "--name", name,
+ "--user-allowed-to-authenticate-from",
+ sddl,
+ "--user-allowed-to-authenticate-from-device-silo",
+ "Managers")
+
+ self.assertEqual(result, -1)
+ self.assertIn("--user-allowed-to-authenticate-from argument repeated 2 times.", err)
+
+ def test_create__user_allowed_to_authenticate_to_repeated(self):
+ """Test repeating similar arguments doesn't make sense to use together.
+
+ --user-allowed-to-authenticate-to
+ --user-allowed-to-authenticate-to-by-silo
+ """
+ sddl = 'O:SYG:SYD:(XA;OICI;CR;;;WD;(@USER.ad://ext/AuthenticationSilo == "Developers"))'
+ name = self.unique_name()
+
+ result, out, err = self.runcmd("domain", "auth", "policy", "create",
+ "--name", name,
+ "--user-allowed-to-authenticate-to",
+ sddl,
+ "--user-allowed-to-authenticate-to-by-silo",
+ "Managers")
+
+ self.assertEqual(result, -1)
+ self.assertIn("--user-allowed-to-authenticate-to argument repeated 2 times.", err)
+
+ def test_create__service_allowed_to_authenticate_from_repeated(self):
+ """Test repeating similar arguments doesn't make sense to use together.
+
+ --service-allowed-to-authenticate-from
+ --service-allowed-to-authenticate-from-device-silo
+ """
+ sddl = 'O:SYG:SYD:(XA;OICI;CR;;;WD;(@USER.ad://ext/AuthenticationSilo == "Managers"))'
+ name = self.unique_name()
+
+ result, out, err = self.runcmd("domain", "auth", "policy", "create",
+ "--name", name,
+ "--service-allowed-to-authenticate-from",
+ sddl,
+ "--service-allowed-to-authenticate-from-device-silo",
+ "QA")
+
+ self.assertEqual(result, -1)
+ self.assertIn("--service-allowed-to-authenticate-from argument repeated 2 times.", err)
+
+ def test_create__service_allowed_to_authenticate_to_repeated(self):
+ """Test repeating similar arguments doesn't make sense to use together.
+
+ --service-allowed-to-authenticate-to
+ --service-allowed-to-authenticate-to-by-silo
+ """
+ sddl = 'O:SYG:SYD:(XA;OICI;CR;;;WD;(@USER.ad://ext/AuthenticationSilo == "Managers"))'
+ name = self.unique_name()
+
+ result, out, err = self.runcmd("domain", "auth", "policy", "create",
+ "--name", name,
+ "--service-allowed-to-authenticate-to",
+ sddl,
+ "--service-allowed-to-authenticate-to-by-silo",
+ "QA")
+
+ self.assertEqual(result, -1)
+ self.assertIn("--service-allowed-to-authenticate-to argument repeated 2 times.", err)
+
+ def test_create__computer_allowed_to_authenticate_to_repeated(self):
+ """Test repeating similar arguments doesn't make sense to use together.
+
+ --computer-allowed-to-authenticate-to
+ --computer-allowed-to-authenticate-to-by-silo
+ """
+ sddl = 'O:SYG:SYD:(XA;OICI;CR;;;WD;(@USER.ad://ext/AuthenticationSilo == "Managers"))'
+ name = self.unique_name()
+
+ result, out, err = self.runcmd("domain", "auth", "policy", "create",
+ "--name", name,
+ "--computer-allowed-to-authenticate-to",
+ sddl,
+ "--computer-allowed-to-authenticate-to-by-silo",
+ "QA")
+
+ self.assertEqual(result, -1)
+ self.assertIn("--computer-allowed-to-authenticate-to argument repeated 2 times.", err)
+
+ def test_create__fails(self):
+ """Test creating an authentication policy, but it fails."""
+ name = self.unique_name()
+
+ # Raise ModelError when ldb.add() is called.
+ with patch.object(SamDB, "add") as add_mock:
+ add_mock.side_effect = ModelError("Custom error message")
+ result, out, err = self.runcmd("domain", "auth", "policy", "create",
+ "--name", name)
+ self.assertEqual(result, -1)
+ self.assertIn("Custom error message", err)
+
+ def test_modify__description(self):
+ """Test modifying an authentication policy description."""
+ name = self.unique_name()
+
+ # Create a policy to modify for this test.
+ self.addCleanup(self.delete_authentication_policy, name=name, force=True)
+ self.runcmd("domain", "auth", "policy", "create", "--name", name)
+
+ # Change the policy description.
+ result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+ "--name", name,
+ "--description", "NewDescription")
+ self.assertIsNone(result, msg=err)
+
+ # Verify fields were changed.
+ policy = self.get_authentication_policy(name)
+ self.assertEqual(str(policy["description"]), "NewDescription")
+
+ def test_modify__strong_ntlm_policy(self):
+ """Test modify strong ntlm policy on the authentication policy."""
+ name = self.unique_name()
+
+ # Create a policy to modify for this test.
+ self.addCleanup(self.delete_authentication_policy, name=name, force=True)
+ self.runcmd("domain", "auth", "policy", "create", "--name", name)
+
+ result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+ "--name", name,
+ "--strong-ntlm-policy", "Required")
+ self.assertIsNone(result, msg=err)
+
+ # Verify fields were changed.
+ policy = self.get_authentication_policy(name)
+ self.assertEqual(str(policy["msDS-StrongNTLMPolicy"]), "2")
+
+ # Check an invalid choice.
+ with self.assertRaises((OptionValueError, SystemExit)):
+ self.runcmd("domain", "auth", "policy", "modify",
+ "--name", name,
+ "--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_modify__user_tgt_lifetime_mins(self):
+ """Test modifying an authentication policy --user-tgt-lifetime-mins.
+
+ This includes checking the upper and lower bounds.
+ """
+ name = self.unique_name()
+
+ # Create a policy to modify for this test.
+ self.addCleanup(self.delete_authentication_policy, name=name, force=True)
+ self.runcmd("domain", "auth", "policy", "create", "--name", name)
+
+ result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+ "--name", name,
+ "--user-tgt-lifetime-mins", "120")
+ self.assertIsNone(result, msg=err)
+
+ # Verify field was changed.
+ policy = self.get_authentication_policy(name)
+ self.assertEqual(str(policy["msDS-UserTGTLifetime"]), "120")
+
+ # check lower bounds (45)
+ result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+ "--name", name + "Lower",
+ "--user-tgt-lifetime-mins", "44")
+ self.assertEqual(result, -1)
+ self.assertIn("--user-tgt-lifetime-mins must be between 45 and 2147483647",
+ err)
+
+ # check upper bounds (2147483647)
+ result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+ "--name", name + "Upper",
+ "--user-tgt-lifetime-mins", "2147483648")
+ self.assertEqual(result, -1)
+ self.assertIn("--user-tgt-lifetime-mins must be between 45 and 2147483647",
+ err)
+
+ def test_modify__service_tgt_lifetime_mins(self):
+ """Test modifying an authentication policy --service-tgt-lifetime-mins.
+
+ This includes checking the upper and lower bounds.
+ """
+ name = self.unique_name()
+
+ # Create a policy to modify for this test.
+ self.addCleanup(self.delete_authentication_policy, name=name, force=True)
+ self.runcmd("domain", "auth", "policy", "create", "--name", name)
+
+ result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+ "--name", name,
+ "--service-tgt-lifetime-mins", "120")
+ self.assertIsNone(result, msg=err)
+
+ # Verify field was changed.
+ policy = self.get_authentication_policy(name)
+ self.assertEqual(str(policy["msDS-ServiceTGTLifetime"]), "120")
+
+ # check lower bounds (45)
+ result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+ "--name", name + "Lower",
+ "--service-tgt-lifetime-mins", "44")
+ self.assertEqual(result, -1)
+ self.assertIn("--service-tgt-lifetime-mins must be between 45 and 2147483647",
+ err)
+
+ # check upper bounds (2147483647)
+ result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+ "--name", name + "Upper",
+ "--service-tgt-lifetime-mins", "2147483648")
+ self.assertEqual(result, -1)
+ self.assertIn("--service-tgt-lifetime-mins must be between 45 and 2147483647",
+ err)
+
+ def test_modify__computer_tgt_lifetime_mins(self):
+ """Test modifying an authentication policy --computer-tgt-lifetime-mins.
+
+ This includes checking the upper and lower bounds.
+ """
+ name = self.unique_name()
+
+ # Create a policy to modify for this test.
+ self.addCleanup(self.delete_authentication_policy, name=name, force=True)
+ self.runcmd("domain", "auth", "policy", "create", "--name", name)
+
+ result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+ "--name", name,
+ "--computer-tgt-lifetime-mins", "120")
+ self.assertIsNone(result, msg=err)
+
+ # Verify field was changed.
+ policy = self.get_authentication_policy(name)
+ self.assertEqual(str(policy["msDS-ComputerTGTLifetime"]), "120")
+
+ # check lower bounds (45)
+ result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+ "--name", name + "Lower",
+ "--computer-tgt-lifetime-mins", "44")
+ self.assertEqual(result, -1)
+ self.assertIn("--computer-tgt-lifetime-mins must be between 45 and 2147483647",
+ err)
+
+ # check upper bounds (2147483647)
+ result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+ "--name", name + "Upper",
+ "--computer-tgt-lifetime-mins", "2147483648")
+ self.assertEqual(result, -1)
+ self.assertIn("--computer-tgt-lifetime-mins must be between 45 and 2147483647",
+ err)
+
+ def test_modify__user_allowed_to_authenticate_from(self):
+ """Modify authentication policy user allowed to authenticate from."""
+ name = self.unique_name()
+ expected = "O:SYG:SYD:(XA;OICI;CR;;;WD;(Member_of {SID(AO)}))"
+
+ # Create a policy to modify for this test.
+ self.addCleanup(self.delete_authentication_policy, name=name, force=True)
+ self.runcmd("domain", "auth", "policy", "create", "--name", name)
+
+ # Modify user allowed to authenticate from field
+ result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+ "--name", name,
+ "--user-allowed-to-authenticate-from",
+ expected)
+ self.assertIsNone(result, msg=err)
+
+ # Check user allowed to authenticate from field was modified.
+ policy = self.get_authentication_policy(name)
+ self.assertEqual(str(policy["cn"]), name)
+ desc = policy["msDS-UserAllowedToAuthenticateFrom"][0]
+ sddl = ndr_unpack(security.descriptor, desc).as_sddl()
+ self.assertEqual(sddl, expected)
+
+ def test_modify__user_allowed_to_authenticate_from_device_group(self):
+ """Test the --user-allowed-to-authenticate-from-device-group shortcut."""
+ name = self.unique_name()
+ expected = "O:SYG:SYD:(XA;OICI;CR;;;WD;(Member_of_any {SID(%s)}))" % (
+ self.device_group.object_sid)
+
+ # Create a policy to modify for this test.
+ self.addCleanup(self.delete_authentication_policy, name=name, force=True)
+ self.runcmd("domain", "auth", "policy", "create", "--name", name)
+
+ # Modify user allowed to authenticate from silo field
+ result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+ "--name", name,
+ "--user-allowed-to-authenticate-from-device-group",
+ self.device_group.name)
+ self.assertIsNone(result, msg=err)
+
+ # Check generated SDDL.
+ policy = self.get_authentication_policy(name)
+ desc = policy["msDS-UserAllowedToAuthenticateFrom"][0]
+ sddl = ndr_unpack(security.descriptor, desc).as_sddl()
+ self.assertEqual(sddl, expected)
+
+ def test_modify__user_allowed_to_authenticate_from_device_silo(self):
+ """Test the --user-allowed-to-authenticate-from-device-silo shortcut."""
+ name = self.unique_name()
+
+ # Create a policy to modify for this test.
+ self.addCleanup(self.delete_authentication_policy, name=name, force=True)
+ self.runcmd("domain", "auth", "policy", "create", "--name", name)
+
+ # Modify user allowed to authenticate from silo field
+ result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+ "--name", name,
+ "--user-allowed-to-authenticate-from-device-silo",
+ "QA")
+ self.assertIsNone(result, msg=err)
+
+ # Check generated SDDL.
+ policy = self.get_authentication_policy(name)
+ desc = policy["msDS-UserAllowedToAuthenticateFrom"][0]
+ sddl = ndr_unpack(security.descriptor, desc).as_sddl()
+ self.assertEqual(
+ sddl,
+ 'O:SYG:SYD:(XA;OICI;CR;;;WD;(@USER.ad://ext/AuthenticationSilo == "QA"))')
+
+ def test_modify__user_allowed_to_authenticate_to(self):
+ """Modify authentication policy user allowed to authenticate to."""
+ name = self.unique_name()
+ expected = "O:SYG:SYD:(XA;OICI;CR;;;WD;(Member_of {SID(AO)}))"
+
+ # Create a policy to modify for this test.
+ self.addCleanup(self.delete_authentication_policy, name=name, force=True)
+ self.runcmd("domain", "auth", "policy", "create", "--name", name)
+
+ # Modify user allowed to authenticate to field
+ result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+ "--name", name,
+ "--user-allowed-to-authenticate-to",
+ expected)
+ self.assertIsNone(result, msg=err)
+
+ # Check user allowed to authenticate to field was modified.
+ policy = self.get_authentication_policy(name)
+ self.assertEqual(str(policy["cn"]), name)
+ desc = policy["msDS-UserAllowedToAuthenticateTo"][0]
+ sddl = ndr_unpack(security.descriptor, desc).as_sddl()
+ self.assertEqual(sddl, expected)
+
+ def test_modify__user_allowed_to_authenticate_to_by_group(self):
+ """Tests the --user-allowed-to-authenticate-to-by-group shortcut."""
+ name = self.unique_name()
+ expected = "O:SYG:SYD:(XA;OICI;CR;;;WD;(Member_of_any {SID(%s)}))" % (
+ self.device_group.object_sid)
+
+ # Create a policy to modify for this test.
+ self.addCleanup(self.delete_authentication_policy, name=name, force=True)
+ self.runcmd("domain", "auth", "policy", "create", "--name", name)
+
+ # Modify user allowed to authenticate to field
+ result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+ "--name", name,
+ "--user-allowed-to-authenticate-to-by-group",
+ self.device_group.name)
+ self.assertIsNone(result, msg=err)
+
+ # Check user allowed to authenticate to field was modified.
+ policy = self.get_authentication_policy(name)
+ self.assertEqual(str(policy["cn"]), name)
+ desc = policy["msDS-UserAllowedToAuthenticateTo"][0]
+ sddl = ndr_unpack(security.descriptor, desc).as_sddl()
+ self.assertEqual(sddl, expected)
+
+ def test_modify__user_allowed_to_authenticate_to_by_silo(self):
+ """Tests the --user-allowed-to-authenticate-to-by-silo shortcut."""
+ name = self.unique_name()
+ expected = ('O:SYG:SYD:(XA;OICI;CR;;;WD;(@USER.ad://ext/'
+ 'AuthenticationSilo == "Developers"))')
+
+ # Create a policy to modify for this test.
+ self.addCleanup(self.delete_authentication_policy, name=name, force=True)
+ self.runcmd("domain", "auth", "policy", "create", "--name", name)
+
+ # Modify user allowed to authenticate to field
+ result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+ "--name", name,
+ "--user-allowed-to-authenticate-to-by-silo",
+ "Developers")
+ self.assertIsNone(result, msg=err)
+
+ # Check user allowed to authenticate to field was modified.
+ policy = self.get_authentication_policy(name)
+ self.assertEqual(str(policy["cn"]), name)
+ desc = policy["msDS-UserAllowedToAuthenticateTo"][0]
+ sddl = ndr_unpack(security.descriptor, desc).as_sddl()
+ self.assertEqual(sddl, expected)
+
+ def test_modify__service_allowed_to_authenticate_from(self):
+ """Modify authentication policy service allowed to authenticate from."""
+ name = self.unique_name()
+ expected = "O:SYG:SYD:(XA;OICI;CR;;;WD;(Member_of {SID(AO)}))"
+
+ # Create a policy to modify for this test.
+ self.addCleanup(self.delete_authentication_policy, name=name, force=True)
+ self.runcmd("domain", "auth", "policy", "create", "--name", name)
+
+ # Modify service allowed to authenticate from field
+ result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+ "--name", name,
+ "--service-allowed-to-authenticate-from",
+ expected)
+ self.assertIsNone(result, msg=err)
+
+ # Check service allowed to authenticate from field was modified.
+ policy = self.get_authentication_policy(name)
+ self.assertEqual(str(policy["cn"]), name)
+ desc = policy["msDS-ServiceAllowedToAuthenticateFrom"][0]
+ sddl = ndr_unpack(security.descriptor, desc).as_sddl()
+ self.assertEqual(sddl, expected)
+
+ def test_modify__service_allowed_to_authenticate_from_device_group(self):
+ """Test the --service-allowed-to-authenticate-from-device-group shortcut."""
+ name = self.unique_name()
+ expected = "O:SYG:SYD:(XA;OICI;CR;;;WD;(Member_of_any {SID(%s)}))" % (
+ self.device_group.object_sid)
+
+ # Create a policy to modify for this test.
+ self.addCleanup(self.delete_authentication_policy, name=name, force=True)
+ self.runcmd("domain", "auth", "policy", "create", "--name", name)
+
+ # Modify user allowed to authenticate from silo field
+ result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+ "--name", name,
+ "--service-allowed-to-authenticate-from-device-group",
+ self.device_group.name)
+ self.assertIsNone(result, msg=err)
+
+ # Check generated SDDL.
+ policy = self.get_authentication_policy(name)
+ desc = policy["msDS-ServiceAllowedToAuthenticateFrom"][0]
+ sddl = ndr_unpack(security.descriptor, desc).as_sddl()
+ self.assertEqual(sddl, expected)
+
+ def test_modify__service_allowed_to_authenticate_from_device_silo(self):
+ """Test the --service-allowed-to-authenticate-from-device-silo shortcut."""
+ name = self.unique_name()
+
+ # Create a policy to modify for this test.
+ self.addCleanup(self.delete_authentication_policy, name=name, force=True)
+ self.runcmd("domain", "auth", "policy", "create", "--name", name)
+
+ # Modify user allowed to authenticate from silo field
+ result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+ "--name", name,
+ "--service-allowed-to-authenticate-from-device-silo",
+ "Developers")
+ self.assertIsNone(result, msg=err)
+
+ # Check generated SDDL.
+ policy = self.get_authentication_policy(name)
+ desc = policy["msDS-ServiceAllowedToAuthenticateFrom"][0]
+ sddl = ndr_unpack(security.descriptor, desc).as_sddl()
+ self.assertEqual(
+ sddl,
+ 'O:SYG:SYD:(XA;OICI;CR;;;WD;(@USER.ad://ext/AuthenticationSilo == "Developers"))')
+
+ def test_modify__service_allowed_to_authenticate_to(self):
+ """Modify authentication policy service allowed to authenticate to."""
+ name = self.unique_name()
+ expected = "O:SYG:SYD:(XA;OICI;CR;;;WD;(Member_of {SID(AO)}))"
+
+ # Create a policy to modify for this test.
+ self.addCleanup(self.delete_authentication_policy, name=name, force=True)
+ self.runcmd("domain", "auth", "policy", "create", "--name", name)
+
+ # Modify service allowed to authenticate to field
+ result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+ "--name", name,
+ "--service-allowed-to-authenticate-to",
+ expected)
+ self.assertIsNone(result, msg=err)
+
+ # Check service allowed to authenticate to field was modified.
+ policy = self.get_authentication_policy(name)
+ self.assertEqual(str(policy["cn"]), name)
+ desc = policy["msDS-ServiceAllowedToAuthenticateTo"][0]
+ sddl = ndr_unpack(security.descriptor, desc).as_sddl()
+ self.assertEqual(sddl, expected)
+
+ def test_modify__service_allowed_to_authenticate_to_by_group(self):
+ """Tests the --service-allowed-to-authenticate-to-by-group shortcut."""
+ name = self.unique_name()
+ expected = "O:SYG:SYD:(XA;OICI;CR;;;WD;(Member_of_any {SID(%s)}))" % (
+ self.device_group.object_sid)
+
+ # Create a policy to modify for this test.
+ self.addCleanup(self.delete_authentication_policy, name=name, force=True)
+ self.runcmd("domain", "auth", "policy", "create", "--name", name)
+
+ # Modify user allowed to authenticate to field
+ result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+ "--name", name,
+ "--service-allowed-to-authenticate-to-by-group",
+ self.device_group.name)
+ self.assertIsNone(result, msg=err)
+
+ # Check user allowed to authenticate to field was modified.
+ policy = self.get_authentication_policy(name)
+ self.assertEqual(str(policy["cn"]), name)
+ desc = policy["msDS-ServiceAllowedToAuthenticateTo"][0]
+ sddl = ndr_unpack(security.descriptor, desc).as_sddl()
+ self.assertEqual(sddl, expected)
+
+ def test_modify__service_allowed_to_authenticate_to_by_silo(self):
+ """Tests the --service-allowed-to-authenticate-to-by-silo shortcut."""
+ name = self.unique_name()
+ expected = ('O:SYG:SYD:(XA;OICI;CR;;;WD;(@USER.ad://ext/'
+ 'AuthenticationSilo == "QA"))')
+
+ # Create a policy to modify for this test.
+ self.addCleanup(self.delete_authentication_policy, name=name, force=True)
+ self.runcmd("domain", "auth", "policy", "create", "--name", name)
+
+ # Modify user allowed to authenticate to field
+ result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+ "--name", name,
+ "--service-allowed-to-authenticate-to-by-silo",
+ "QA")
+ self.assertIsNone(result, msg=err)
+
+ # Check user allowed to authenticate to field was modified.
+ policy = self.get_authentication_policy(name)
+ self.assertEqual(str(policy["cn"]), name)
+ desc = policy["msDS-ServiceAllowedToAuthenticateTo"][0]
+ sddl = ndr_unpack(security.descriptor, desc).as_sddl()
+ self.assertEqual(sddl, expected)
+
+ def test_modify__computer_allowed_to_authenticate_to(self):
+ """Modify authentication policy computer allowed to authenticate to."""
+ name = self.unique_name()
+ expected = "O:SYG:SYD:(XA;OICI;CR;;;WD;(Member_of {SID(AO)}))"
+
+ # Create a policy to modify for this test.
+ self.addCleanup(self.delete_authentication_policy, name=name, force=True)
+ self.runcmd("domain", "auth", "policy", "create", "--name", name)
+
+ # Modify computer allowed to authenticate to field
+ result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+ "--name", name,
+ "--computer-allowed-to-authenticate-to",
+ expected)
+ self.assertIsNone(result, msg=err)
+
+ # Check computer allowed to authenticate to field was modified.
+ policy = self.get_authentication_policy(name)
+ self.assertEqual(str(policy["cn"]), name)
+ desc = policy["msDS-ComputerAllowedToAuthenticateTo"][0]
+ sddl = ndr_unpack(security.descriptor, desc).as_sddl()
+ self.assertEqual(sddl, expected)
+
+ def test_modify__computer_allowed_to_authenticate_to_by_group(self):
+ """Tests the --computer-allowed-to-authenticate-to-by-group shortcut."""
+ name = self.unique_name()
+ expected = "O:SYG:SYD:(XA;OICI;CR;;;WD;(Member_of_any {SID(%s)}))" % (
+ self.device_group.object_sid)
+
+ # Create a policy to modify for this test.
+ self.addCleanup(self.delete_authentication_policy, name=name, force=True)
+ self.runcmd("domain", "auth", "policy", "create", "--name", name)
+
+ # Modify user allowed to authenticate to field
+ result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+ "--name", name,
+ "--computer-allowed-to-authenticate-to-by-group",
+ self.device_group.name)
+ self.assertIsNone(result, msg=err)
+
+ # Check user allowed to authenticate to field was modified.
+ policy = self.get_authentication_policy(name)
+ self.assertEqual(str(policy["cn"]), name)
+ desc = policy["msDS-ComputerAllowedToAuthenticateTo"][0]
+ sddl = ndr_unpack(security.descriptor, desc).as_sddl()
+ self.assertEqual(sddl, expected)
+
+ def test_modify__computer_allowed_to_authenticate_to_by_silo(self):
+ """Tests the --computer-allowed-to-authenticate-to-by-silo shortcut."""
+ name = self.unique_name()
+ expected = ('O:SYG:SYD:(XA;OICI;CR;;;WD;(@USER.ad://ext/'
+ 'AuthenticationSilo == "QA"))')
+
+ # Create a policy to modify for this test.
+ self.addCleanup(self.delete_authentication_policy, name=name, force=True)
+ self.runcmd("domain", "auth", "policy", "create", "--name", name)
+
+ # Modify user allowed to authenticate to field
+ result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+ "--name", name,
+ "--computer-allowed-to-authenticate-to-by-silo",
+ "QA")
+ self.assertIsNone(result, msg=err)
+
+ # Check user allowed to authenticate to field was modified.
+ policy = self.get_authentication_policy(name)
+ self.assertEqual(str(policy["cn"]), name)
+ desc = policy["msDS-ComputerAllowedToAuthenticateTo"][0]
+ sddl = ndr_unpack(security.descriptor, desc).as_sddl()
+ self.assertEqual(sddl, expected)
+
+ def test_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_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("Authentication policy doesNotExist not found.", err)
+
+ def test_modify__audit_enforce(self):
+ """Test modify authentication policy using --audit and --enforce."""
+ name = self.unique_name()
+
+ # Create a policy to modify for this test.
+ self.addCleanup(self.delete_authentication_policy,
+ name=name, force=True)
+ self.runcmd("domain", "auth", "policy", "create", "--name", name)
+
+ # Change to audit, the default is --enforce.
+ result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+ "--name", name,
+ "--audit")
+ self.assertIsNone(result, msg=err)
+
+ # Check that the policy was changed to --audit.
+ policy = self.get_authentication_policy(name)
+ self.assertEqual(str(policy["msDS-AuthNPolicyEnforced"]), "FALSE")
+
+ result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+ "--name", name,
+ "--enforce")
+ self.assertIsNone(result, msg=err)
+
+ # Check if the policy was changed back to --enforce.
+ policy = self.get_authentication_policy(name)
+ self.assertEqual(str(policy["msDS-AuthNPolicyEnforced"]), "TRUE")
+
+ def test_modify__protect_unprotect(self):
+ """Test modify authentication policy using --protect and --unprotect."""
+ name = self.unique_name()
+
+ # Create a policy to modify for this test.
+ self.addCleanup(self.delete_authentication_policy, name=name, force=True)
+ self.runcmd("domain", "auth", "policy", "create", "--name", name)
+
+ utils = SDUtils(self.samdb)
+ result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+ "--name", name,
+ "--protect")
+ self.assertIsNone(result, msg=err)
+
+ # Check that claim type was protected.
+ policy = self.get_authentication_policy(name)
+ desc = utils.get_sd_as_sddl(policy["dn"])
+ self.assertIn("(D;;DTSD;;;WD)", desc)
+
+ result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+ "--name", name,
+ "--unprotect")
+ self.assertIsNone(result, msg=err)
+
+ # Check that claim type was unprotected.
+ policy = self.get_authentication_policy(name)
+ desc = utils.get_sd_as_sddl(policy["dn"])
+ self.assertNotIn("(D;;DTSD;;;WD)", desc)
+
+ def test_modify__audit_enforce_together(self):
+ """Test modify auth policy using both --audit and --enforce."""
+ result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+ "--name", "User Policy",
+ "--audit", "--enforce")
+ self.assertEqual(result, -1)
+ self.assertIn("--audit and --enforce cannot be used together.", err)
+
+ def test_modify__protect_unprotect_together(self):
+ """Test modify authentication policy using --protect and --unprotect."""
+ result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+ "--name", "User Policy",
+ "--protect", "--unprotect")
+ self.assertEqual(result, -1)
+ self.assertIn("--protect and --unprotect cannot be used together.", err)
+
+ def test_modify__fails(self):
+ """Test modifying an authentication policy, but it fails."""
+ # Raise ModelError when ldb.add() is called.
+ with patch.object(SamDB, "modify") as modify_mock:
+ modify_mock.side_effect = ModelError("Custom error message")
+ result, out, err = self.runcmd("domain", "auth", "policy", "modify",
+ "--name", "User Policy",
+ "--description", "New description")
+ self.assertEqual(result, -1)
+ self.assertIn("Custom error message", err)
+
+ def test_delete__success(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_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_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_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_delete__force_fails(self):
+ """Test deleting an authentication policy with --force, but it fails."""
+ name = self.unique_name()
+
+ # Create protected authentication policy.
+ self.addCleanup(self.delete_authentication_policy, name=name, force=True)
+ result, out, err = self.runcmd("domain", "auth", "policy", "create",
+ "--name", name,
+ "--protect")
+ self.assertIsNone(result, msg=err)
+
+ # Policy exists
+ policy = self.get_authentication_policy(name)
+ self.assertIsNotNone(policy)
+
+ # Try doing delete with --force.
+ # Patch SDUtils.dacl_delete_aces with a Mock that raises ModelError.
+ with patch.object(SDUtils, "dacl_delete_aces") as delete_mock:
+ delete_mock.side_effect = ModelError("Custom error message")
+ result, out, err = self.runcmd("domain", "auth", "policy", "delete",
+ "--name", name,
+ "--force")
+ self.assertEqual(result, -1)
+ self.assertIn("Custom error message", err)
+
+ def test_delete__fails(self):
+ """Test deleting an authentication policy, but it fails."""
+ name = self.unique_name()
+
+ # Create regular authentication policy.
+ self.addCleanup(self.delete_authentication_policy, name=name, force=True)
+ result, out, err = self.runcmd("domain", "auth", "policy", "create",
+ "--name", name)
+ self.assertIsNone(result, msg=err)
+
+ # Policy exists
+ policy = self.get_authentication_policy(name)
+ self.assertIsNotNone(policy)
+
+ # Raise ModelError when ldb.delete() is called.
+ with patch.object(SamDB, "delete") as delete_mock:
+ delete_mock.side_effect = ModelError("Custom error message")
+ result, out, err = self.runcmd("domain", "auth", "policy", "delete",
+ "--name", name)
+ self.assertEqual(result, -1)
+ self.assertIn("Custom error message", err)
+
+ # When not using --force we get a hint.
+ self.assertIn("Try --force", err)
+
+ def test_delete__protected_fails(self):
+ """Test deleting an authentication policy, but it fails."""
+ name = self.unique_name()
+
+ # Create protected authentication policy.
+ self.addCleanup(self.delete_authentication_policy, name=name, force=True)
+ result, out, err = self.runcmd("domain", "auth", "policy", "create",
+ "--name", name,
+ "--protect")
+ self.assertIsNone(result, msg=err)
+
+ # Policy exists
+ policy = self.get_authentication_policy(name)
+ self.assertIsNotNone(policy)
+
+ # Raise ModelError when ldb.delete() is called.
+ with patch.object(SamDB, "delete") as delete_mock:
+ delete_mock.side_effect = ModelError("Custom error message")
+ result, out, err = self.runcmd("domain", "auth", "policy", "delete",
+ "--name", name,
+ "--force")
+ self.assertEqual(result, -1)
+ self.assertIn("Custom error message", err)
+
+ # When using --force we don't get the hint.
+ self.assertNotIn("Try --force", err)