diff options
Diffstat (limited to 'python/samba/tests/samba_tool/domain_auth_policy.py')
-rw-r--r-- | python/samba/tests/samba_tool/domain_auth_policy.py | 1517 |
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) |