# Unix SMB/CIFS implementation. # # Tests for samba-tool domain auth policy command # # Copyright (C) Catalyst.Net Ltd. 2023 # # Written by Rob van der Linde # # 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 . # import json from optparse import OptionValueError from unittest.mock import patch from samba.dcerpc import security from samba.domain.models.exceptions import ModelError from samba.ndr import ndr_pack, ndr_unpack from samba.nt_time import NT_TICKS_PER_SEC from samba.samdb import SamDB from samba.sd_utils import SDUtils from .silo_base import SiloTest def mins_to_tgt_lifetime(minutes): """Convert minutes to the tgt_lifetime attributes unit which is 10^-7 seconds""" if minutes is not None: return minutes * 60 * NT_TICKS_PER_SEC return minutes 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"]), str(mins_to_tgt_lifetime(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__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"]), str(mins_to_tgt_lifetime(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__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"]), str(mins_to_tgt_lifetime(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__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_user_allowed_to_authenticate_from__set_repeated(self): """Test repeating similar arguments doesn't make sense to use together. user-allowed-to-authenticate-from set --device-group user-allowed-to-authenticate-from set --device-silo """ name = self.unique_name() self.runcmd("domain", "auth", "policy", "create", "--name", name) self.addCleanup(self.delete_authentication_policy, name=name, force=True) result, out, err = self.runcmd("domain", "auth", "policy", "user-allowed-to-authenticate-from", "set", "--name", name, "--device-group", self.device_group.name, "--device-silo", "Managers") self.assertEqual(result, -1) self.assertIn("Cannot have both --device-group and --device-silo options.", err) def test_user_allowed_to_authenticate_to__set_repeated(self): """Test repeating similar arguments doesn't make sense to use together. user-allowed-to-authenticate-to set --by-group user-allowed-to-authenticate-to set --by-silo """ name = self.unique_name() self.runcmd("domain", "auth", "policy", "create", "--name", name) self.addCleanup(self.delete_authentication_policy, name=name, force=True) result, out, err = self.runcmd("domain", "auth", "policy", "user-allowed-to-authenticate-to", "set", "--name", name, "--by-group", self.device_group.name, "--by-silo", "Managers") self.assertEqual(result, -1) self.assertIn("Cannot have both --by-group and --by-silo options.", err) def test_service_allowed_to_authenticate_from__set_repeated(self): """Test repeating similar arguments doesn't make sense to use together. service-allowed-to-authenticate-from set --device-group service-allowed-to-authenticate-from set --device-silo """ name = self.unique_name() self.runcmd("domain", "auth", "policy", "create", "--name", name) self.addCleanup(self.delete_authentication_policy, name=name, force=True) result, out, err = self.runcmd("domain", "auth", "policy", "service-allowed-to-authenticate-from", "set", "--name", name, "--device-group", self.device_group.name, "--device-silo", "QA") self.assertEqual(result, -1) self.assertIn("Cannot have both --device-group and --device-silo options.", err) def test_service_allowed_to_authenticate_to__set_repeated(self): """Test repeating similar arguments doesn't make sense to use together. service-allowed-to-authenticate-to set --by-group service-allowed-to-authenticate-to set --by-silo """ name = self.unique_name() self.runcmd("domain", "auth", "policy", "create", "--name", name) self.addCleanup(self.delete_authentication_policy, name=name, force=True) result, out, err = self.runcmd("domain", "auth", "policy", "service-allowed-to-authenticate-to", "set", "--name", name, "--by-group", self.device_group.name, "--by-silo", "QA") self.assertEqual(result, -1) self.assertIn("Cannot have both --by-group and --by-silo options.", err) def test_computer_allowed_to_authenticate_to__set_repeated(self): """Test repeating similar arguments doesn't make sense to use together. computer-allowed-to-authenticate-to set --by-group computer-allowed-to-authenticate-to set --by-silo """ name = self.unique_name() self.runcmd("domain", "auth", "policy", "create", "--name", name) self.addCleanup(self.delete_authentication_policy, name=name, force=True) result, out, err = self.runcmd("domain", "auth", "policy", "computer-allowed-to-authenticate-to", "set", "--name", name, "--by-group", self.device_group.name, "--by-silo", "QA") self.assertEqual(result, -1) self.assertIn("Cannot have both --by-group and --by-silo options.", 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"]), str(mins_to_tgt_lifetime(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"]), str(mins_to_tgt_lifetime(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"]), str(mins_to_tgt_lifetime(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_user_allowed_to_authenticate_from__set_device_group(self): """Tests the user-allowed-to-authenticate-from set --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", "user-allowed-to-authenticate-from", "set", "--name", name, "--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_user_allowed_to_authenticate_from__set_device_silo(self): """Tests the user-allowed-to-authenticate-from set --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", "user-allowed-to-authenticate-from", "set", "--name", name, "--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_user_allowed_to_authenticate_to__set_by_group(self): """Tests the user-allowed-to-authenticate-to set --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", "user-allowed-to-authenticate-to", "set", "--name", name, "--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_user_allowed_to_authenticate_to__set_by_silo(self): """Tests the user-allowed-to-authenticate-to set --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", "user-allowed-to-authenticate-to", "set", "--name", name, "--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_service_allowed_to_authenticate_from__set_device_group(self): """Tests the service-allowed-to-authenticate-from set --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", "service-allowed-to-authenticate-from", "set", "--name", name, "--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_service_allowed_to_authenticate_from__set_device_silo(self): """Tests the service-allowed-to-authenticate-from set --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", "service-allowed-to-authenticate-from", "set", "--name", name, "--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_service_allowed_to_authenticate_to__set_by_group(self): """Tests the service-allowed-to-authenticate-to set --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", "service-allowed-to-authenticate-to", "set", "--name", name, "--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_service_allowed_to_authenticate_to__set_by_silo(self): """Tests the service-allowed-to-authenticate-to set --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", "service-allowed-to-authenticate-to", "set", "--name", name, "--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_computer_allowed_to_authenticate_to__set_by_group(self): """Tests the computer-allowed-to-authenticate-to set --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", "computer-allowed-to-authenticate-to", "set", "--name", name, "--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_computer_allowed_to_authenticate_to__set_by_silo(self): """Tests the computer-allowed-to-authenticate-to set --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", "computer-allowed-to-authenticate-to", "set", "--name", name, "--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)