1 # Unix SMB/CIFS implementation.
3 # Tests for samba-tool domain auth policy command
5 # Copyright (C) Catalyst.Net Ltd. 2023
7 # Written by Rob van der Linde <rob@catalyst.net.nz>
9 # This program is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 3 of the License, or
12 # (at your option) any later version.
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 from optparse import OptionValueError
25 from unittest.mock import patch
27 from samba.dcerpc import security
28 from samba.ndr import ndr_unpack
29 from samba.netcmd.domain.models.exceptions import ModelError
30 from samba.samdb import SamDB
31 from samba.sd_utils import SDUtils
33 from .domain_auth_base import BaseAuthCmdTest
36 class AuthPolicyCmdTestCase(BaseAuthCmdTest):
39 """Test listing authentication policies in list format."""
40 result, out, err = self.runcmd("domain", "auth", "policy", "list")
41 self.assertIsNone(result, msg=err)
43 expected_policies = ["User Policy", "Service Policy", "Computer Policy"]
45 for policy in expected_policies:
46 self.assertIn(policy, out)
48 def test_list__json(self):
49 """Test listing authentication policies in JSON format."""
50 result, out, err = self.runcmd("domain", "auth", "policy",
52 self.assertIsNone(result, msg=err)
54 # we should get valid json
55 policies = json.loads(out)
57 expected_policies = ["User Policy", "Service Policy", "Computer Policy"]
59 for name in expected_policies:
60 policy = policies[name]
61 self.assertIn("name", policy)
62 self.assertIn("msDS-AuthNPolicy", list(policy["objectClass"]))
63 self.assertIn("msDS-AuthNPolicyEnforced", policy)
64 self.assertIn("msDS-StrongNTLMPolicy", policy)
65 self.assertIn("objectGUID", policy)
68 """Test viewing a single authentication policy."""
69 result, out, err = self.runcmd("domain", "auth", "policy", "view",
70 "--name", "User Policy")
71 self.assertIsNone(result, msg=err)
73 # we should get valid json
74 policy = json.loads(out)
76 # check a few fields only
77 self.assertEqual(policy["cn"], "User Policy")
78 self.assertEqual(policy["msDS-AuthNPolicyEnforced"], True)
80 def test_view__notfound(self):
81 """Test viewing an authentication policy that doesn't exist."""
82 result, out, err = self.runcmd("domain", "auth", "policy", "view",
83 "--name", "doesNotExist")
84 self.assertEqual(result, -1)
85 self.assertIn("Authentication policy doesNotExist not found.", err)
87 def test_view__name_required(self):
88 """Test view authentication policy without --name argument."""
89 result, out, err = self.runcmd("domain", "auth", "policy", "view")
90 self.assertEqual(result, -1)
91 self.assertIn("Argument --name is required.", err)
93 def test_create__success(self):
94 """Test creating a new authentication policy."""
95 name = self.unique_name()
97 self.addCleanup(self.delete_authentication_policy, name=name, force=True)
98 result, out, err = self.runcmd("domain", "auth", "policy", "create",
100 self.assertIsNone(result, msg=err)
102 # Check policy that was created
103 policy = self.get_authentication_policy(name)
104 self.assertEqual(str(policy["cn"]), name)
105 self.assertEqual(str(policy["msDS-AuthNPolicyEnforced"]), "TRUE")
107 def test_create__description(self):
108 """Test creating a new authentication policy with description set."""
109 name = self.unique_name()
111 self.addCleanup(self.delete_authentication_policy, name=name, force=True)
112 result, out, err = self.runcmd("domain", "auth", "policy", "create",
114 "--description", "Custom Description")
115 self.assertIsNone(result, msg=err)
117 # Check policy description
118 policy = self.get_authentication_policy(name)
119 self.assertEqual(str(policy["cn"]), name)
120 self.assertEqual(str(policy["description"]), "Custom Description")
122 def test_create__user_tgt_lifetime_mins(self):
123 """Test create a new authentication policy with --user-tgt-lifetime-mins.
125 Also checks the upper and lower bounds are handled.
127 name = self.unique_name()
129 self.addCleanup(self.delete_authentication_policy, name=name, force=True)
130 result, out, err = self.runcmd("domain", "auth", "policy", "create",
132 "--user-tgt-lifetime-mins", "60")
133 self.assertIsNone(result, msg=err)
135 # Check policy fields.
136 policy = self.get_authentication_policy(name)
137 self.assertEqual(str(policy["cn"]), name)
138 self.assertEqual(str(policy["msDS-UserTGTLifetime"]), "60")
140 # check lower bounds (45)
141 result, out, err = self.runcmd("domain", "auth", "policy", "create",
142 "--name", name + "Lower",
143 "--user-tgt-lifetime-mins", "44")
144 self.assertEqual(result, -1)
145 self.assertIn("--user-tgt-lifetime-mins must be between 45 and 2147483647",
148 # check upper bounds (2147483647)
149 result, out, err = self.runcmd("domain", "auth", "policy", "create",
150 "--name", name + "Upper",
151 "--user-tgt-lifetime-mins", "2147483648")
152 self.assertEqual(result, -1)
153 self.assertIn("--user-tgt-lifetime-mins must be between 45 and 2147483647",
156 def test_create__user_allowed_to_authenticate_from_silo(self):
157 """Tests the --user-allowed-to-authenticate-from-silo shortcut."""
158 name = self.unique_name()
160 self.addCleanup(self.delete_authentication_policy, name=name, force=True)
161 result, out, err = self.runcmd("domain", "auth", "policy", "create",
163 "--user-allowed-to-authenticate-from-silo",
165 self.assertIsNone(result, msg=err)
167 # Check policy fields.
168 policy = self.get_authentication_policy(name)
169 self.assertEqual(str(policy["cn"]), name)
171 # Check generated SDDL.
172 desc = policy["msDS-UserAllowedToAuthenticateFrom"][0]
173 sddl = ndr_unpack(security.descriptor, desc).as_sddl()
176 "O:SYG:SYD:(XA;OICI;CR;;;WD;(@USER.ad://ext/AuthenticationSilo/Developers))")
178 def test_create__service_tgt_lifetime_mins(self):
179 """Test create a new authentication policy with --service-tgt-lifetime-mins.
181 Also checks the upper and lower bounds are handled.
183 name = self.unique_name()
185 self.addCleanup(self.delete_authentication_policy, name=name, force=True)
186 result, out, err = self.runcmd("domain", "auth", "policy", "create",
188 "--service-tgt-lifetime-mins", "60")
189 self.assertIsNone(result, msg=err)
191 # Check policy fields.
192 policy = self.get_authentication_policy(name)
193 self.assertEqual(str(policy["cn"]), name)
194 self.assertEqual(str(policy["msDS-ServiceTGTLifetime"]), "60")
196 # check lower bounds (45)
197 result, out, err = self.runcmd("domain", "auth", "policy", "create",
199 "--service-tgt-lifetime-mins", "44")
200 self.assertEqual(result, -1)
201 self.assertIn("--service-tgt-lifetime-mins must be between 45 and 2147483647",
204 # check upper bounds (2147483647)
205 result, out, err = self.runcmd("domain", "auth", "policy", "create",
207 "--service-tgt-lifetime-mins", "2147483648")
208 self.assertEqual(result, -1)
209 self.assertIn("--service-tgt-lifetime-mins must be between 45 and 2147483647",
212 def test_create__service_allowed_to_authenticate_from_silo(self):
213 """Tests the --service-allowed-to-authenticate-from-silo shortcut."""
214 name = self.unique_name()
216 self.addCleanup(self.delete_authentication_policy, name=name, force=True)
217 result, out, err = self.runcmd("domain", "auth", "policy", "create",
219 "--service-allowed-to-authenticate-from-silo",
221 self.assertIsNone(result, msg=err)
223 # Check policy fields.
224 policy = self.get_authentication_policy(name)
225 self.assertEqual(str(policy["cn"]), name)
226 desc = policy["msDS-ServiceAllowedToAuthenticateFrom"][0]
228 # Check generated SDDL.
229 sddl = ndr_unpack(security.descriptor, desc).as_sddl()
232 "O:SYG:SYD:(XA;OICI;CR;;;WD;(@USER.ad://ext/AuthenticationSilo/Managers))")
234 def test_create__computer_tgt_lifetime_mins(self):
235 """Test create a new authentication policy with --computer-tgt-lifetime-mins.
237 Also checks the upper and lower bounds are handled.
239 name = self.unique_name()
241 self.addCleanup(self.delete_authentication_policy, name=name, force=True)
242 result, out, err = self.runcmd("domain", "auth", "policy", "create",
244 "--computer-tgt-lifetime-mins", "60")
245 self.assertIsNone(result, msg=err)
247 # Check policy fields.
248 policy = self.get_authentication_policy(name)
249 self.assertEqual(str(policy["cn"]), name)
250 self.assertEqual(str(policy["msDS-ComputerTGTLifetime"]), "60")
252 # check lower bounds (45)
253 result, out, err = self.runcmd("domain", "auth", "policy", "create",
254 "--name", name + "Lower",
255 "--computer-tgt-lifetime-mins", "44")
256 self.assertEqual(result, -1)
257 self.assertIn("--computer-tgt-lifetime-mins must be between 45 and 2147483647",
260 # check upper bounds (2147483647)
261 result, out, err = self.runcmd("domain", "auth", "policy", "create",
262 "--name", name + "Upper",
263 "--computer-tgt-lifetime-mins", "2147483648")
264 self.assertEqual(result, -1)
265 self.assertIn("--computer-tgt-lifetime-mins must be between 45 and 2147483647",
268 def test_create__valid_sddl(self):
269 """Test creating a new authentication policy with valid SDDL in a field."""
270 name = self.unique_name()
271 expected = "O:SYG:SYD:(XA;OICI;CR;;;WD;(Member_of {SID(AO)}))"
273 self.addCleanup(self.delete_authentication_policy, name=name, force=True)
274 result, out, err = self.runcmd("domain", "auth", "policy", "create",
276 "--user-allowed-to-authenticate-from",
278 self.assertIsNone(result, msg=err)
280 # Check policy fields.
281 policy = self.get_authentication_policy(name)
282 self.assertEqual(str(policy["cn"]), name)
283 desc = policy["msDS-UserAllowedToAuthenticateFrom"][0]
284 sddl = ndr_unpack(security.descriptor, desc).as_sddl()
285 self.assertEqual(sddl, expected)
287 def test_create__invalid_sddl(self):
288 """Test creating a new authentication policy with invalid SDDL in a field."""
289 name = self.unique_name()
291 result, out, err = self.runcmd("domain", "auth", "policy", "create",
293 "--user-allowed-to-authenticate-from",
296 self.assertEqual(result, -1)
298 "msDS-UserAllowedToAuthenticateFrom: Unable to parse SDDL", err)
300 def test_create__already_exists(self):
301 """Test creating a new authentication policy that already exists."""
302 result, out, err = self.runcmd("domain", "auth", "policy", "create",
303 "--name", "User Policy")
304 self.assertEqual(result, -1)
305 self.assertIn("Authentication policy User Policy already exists", err)
307 def test_create__name_missing(self):
308 """Test create authentication policy without --name argument."""
309 result, out, err = self.runcmd("domain", "auth", "policy", "create")
310 self.assertEqual(result, -1)
311 self.assertIn("Argument --name is required.", err)
313 def test_create__audit(self):
314 """Test create authentication policy with --audit flag."""
315 name = self.unique_name()
317 self.addCleanup(self.delete_authentication_policy, name=name, force=True)
318 result, out, err = self.runcmd("domain", "auth", "policy", "create",
321 self.assertIsNone(result, msg=err)
323 # fetch and check policy
324 policy = self.get_authentication_policy(name)
325 self.assertEqual(str(policy["msDS-AuthNPolicyEnforced"]), "FALSE")
327 def test_create__enforce(self):
328 """Test create authentication policy with --enforce flag."""
329 name = self.unique_name()
331 self.addCleanup(self.delete_authentication_policy, name=name, force=True)
332 result, out, err = self.runcmd("domain", "auth", "policy", "create",
335 self.assertIsNone(result, msg=err)
337 # fetch and check policy
338 policy = self.get_authentication_policy(name)
339 self.assertEqual(str(policy["msDS-AuthNPolicyEnforced"]), "TRUE")
341 def test_create__audit_enforce_together(self):
342 """Test create auth policy using both --audit and --enforce."""
343 name = self.unique_name()
345 result, out, err = self.runcmd("domain", "auth", "policy", "create",
347 "--audit", "--enforce")
349 self.assertEqual(result, -1)
350 self.assertIn("--audit and --enforce cannot be used together.", err)
352 def test_create__protect_unprotect_together(self):
353 """Test create authentication policy using --protect and --unprotect."""
354 name = self.unique_name()
356 result, out, err = self.runcmd("domain", "auth", "policy", "create",
358 "--protect", "--unprotect")
360 self.assertEqual(result, -1)
361 self.assertIn("--protect and --unprotect cannot be used together.", err)
363 def test_create__user_allowed_to_authenticate_from_repeated(self):
364 """Test repeating similar arguments doesn't make sense to use together.
366 --user-allowed-to-authenticate-from
367 --user-allowed-to-authenticate-from-silo
369 sddl = "O:SYG:SYD:(XA;OICI;CR;;;WD;(@USER.ad://ext/AuthenticationSilo/Developers))"
370 name = self.unique_name()
372 result, out, err = self.runcmd("domain", "auth", "policy", "create",
374 "--user-allowed-to-authenticate-from",
376 "--user-allowed-to-authenticate-from-silo",
379 self.assertEqual(result, -1)
380 self.assertIn("--user-allowed-to-authenticate-from argument repeated 2 times.", err)
382 def test_create__service_allowed_to_authenticate_from_repeated(self):
383 """Test repeating similar arguments doesn't make sense to use together.
385 --service-allowed-to-authenticate-from
386 --service-allowed-to-authenticate-from-silo
388 sddl = "O:SYG:SYD:(XA;OICI;CR;;;WD;(@USER.ad://ext/AuthenticationSilo/Managers))"
389 name = self.unique_name()
391 result, out, err = self.runcmd("domain", "auth", "policy", "create",
393 "--service-allowed-to-authenticate-from",
395 "--service-allowed-to-authenticate-from-silo",
398 self.assertEqual(result, -1)
399 self.assertIn("--service-allowed-to-authenticate-from argument repeated 2 times.", err)
401 def test_create__fails(self):
402 """Test creating an authentication policy, but it fails."""
403 name = self.unique_name()
405 # Raise ModelError when ldb.add() is called.
406 with patch.object(SamDB, "add") as add_mock:
407 add_mock.side_effect = ModelError("Custom error message")
408 result, out, err = self.runcmd("domain", "auth", "policy", "create",
410 self.assertEqual(result, -1)
411 self.assertIn("Custom error message", err)
413 def test_modify__description(self):
414 """Test modifying an authentication policy description."""
415 name = self.unique_name()
417 # Create a policy to modify for this test.
418 self.addCleanup(self.delete_authentication_policy, name=name, force=True)
419 self.runcmd("domain", "auth", "policy", "create", "--name", name)
421 # Change the policy description.
422 result, out, err = self.runcmd("domain", "auth", "policy", "modify",
424 "--description", "NewDescription")
425 self.assertIsNone(result, msg=err)
427 # Verify fields were changed.
428 policy = self.get_authentication_policy(name)
429 self.assertEqual(str(policy["description"]), "NewDescription")
431 def test_modify__strong_ntlm_policy(self):
432 """Test modify strong ntlm policy on the authentication policy."""
433 name = self.unique_name()
435 # Create a policy to modify for this test.
436 self.addCleanup(self.delete_authentication_policy, name=name, force=True)
437 self.runcmd("domain", "auth", "policy", "create", "--name", name)
439 result, out, err = self.runcmd("domain", "auth", "policy", "modify",
441 "--strong-ntlm-policy", "Required")
442 self.assertIsNone(result, msg=err)
444 # Verify fields were changed.
445 policy = self.get_authentication_policy(name)
446 self.assertEqual(str(policy["msDS-StrongNTLMPolicy"]), "2")
448 # Check an invalid choice.
449 with self.assertRaises((OptionValueError, SystemExit)):
450 self.runcmd("domain", "auth", "policy", "modify",
452 "--strong-ntlm-policy", "Invalid")
454 # It is difficult to test the error message text for invalid
455 # choices because inside optparse it will raise OptionValueError
456 # followed by raising SystemExit(2).
458 def test_modify__user_tgt_lifetime_mins(self):
459 """Test modifying an authentication policy --user-tgt-lifetime-mins.
461 This includes checking the upper and lower bounds.
463 name = self.unique_name()
465 # Create a policy to modify for this test.
466 self.addCleanup(self.delete_authentication_policy, name=name, force=True)
467 self.runcmd("domain", "auth", "policy", "create", "--name", name)
469 result, out, err = self.runcmd("domain", "auth", "policy", "modify",
471 "--user-tgt-lifetime-mins", "120")
472 self.assertIsNone(result, msg=err)
474 # Verify field was changed.
475 policy = self.get_authentication_policy(name)
476 self.assertEqual(str(policy["msDS-UserTGTLifetime"]), "120")
478 # check lower bounds (45)
479 result, out, err = self.runcmd("domain", "auth", "policy", "modify",
480 "--name", name + "Lower",
481 "--user-tgt-lifetime-mins", "44")
482 self.assertEqual(result, -1)
483 self.assertIn("--user-tgt-lifetime-mins must be between 45 and 2147483647",
486 # check upper bounds (2147483647)
487 result, out, err = self.runcmd("domain", "auth", "policy", "modify",
488 "--name", name + "Upper",
489 "--user-tgt-lifetime-mins", "2147483648")
490 self.assertEqual(result, -1)
491 self.assertIn("--user-tgt-lifetime-mins must be between 45 and 2147483647",
494 def test_modify__service_tgt_lifetime_mins(self):
495 """Test modifying an authentication policy --service-tgt-lifetime-mins.
497 This includes checking the upper and lower bounds.
499 name = self.unique_name()
501 # Create a policy to modify for this test.
502 self.addCleanup(self.delete_authentication_policy, name=name, force=True)
503 self.runcmd("domain", "auth", "policy", "create", "--name", name)
505 result, out, err = self.runcmd("domain", "auth", "policy", "modify",
507 "--service-tgt-lifetime-mins", "120")
508 self.assertIsNone(result, msg=err)
510 # Verify field was changed.
511 policy = self.get_authentication_policy(name)
512 self.assertEqual(str(policy["msDS-ServiceTGTLifetime"]), "120")
514 # check lower bounds (45)
515 result, out, err = self.runcmd("domain", "auth", "policy", "modify",
516 "--name", name + "Lower",
517 "--service-tgt-lifetime-mins", "44")
518 self.assertEqual(result, -1)
519 self.assertIn("--service-tgt-lifetime-mins must be between 45 and 2147483647",
522 # check upper bounds (2147483647)
523 result, out, err = self.runcmd("domain", "auth", "policy", "modify",
524 "--name", name + "Upper",
525 "--service-tgt-lifetime-mins", "2147483648")
526 self.assertEqual(result, -1)
527 self.assertIn("--service-tgt-lifetime-mins must be between 45 and 2147483647",
530 def test_modify__computer_tgt_lifetime_mins(self):
531 """Test modifying an authentication policy --computer-tgt-lifetime-mins.
533 This includes checking the upper and lower bounds.
535 name = self.unique_name()
537 # Create a policy to modify for this test.
538 self.addCleanup(self.delete_authentication_policy, name=name, force=True)
539 self.runcmd("domain", "auth", "policy", "create", "--name", name)
541 result, out, err = self.runcmd("domain", "auth", "policy", "modify",
543 "--computer-tgt-lifetime-mins", "120")
544 self.assertIsNone(result, msg=err)
546 # Verify field was changed.
547 policy = self.get_authentication_policy(name)
548 self.assertEqual(str(policy["msDS-ComputerTGTLifetime"]), "120")
550 # check lower bounds (45)
551 result, out, err = self.runcmd("domain", "auth", "policy", "modify",
552 "--name", name + "Lower",
553 "--computer-tgt-lifetime-mins", "44")
554 self.assertEqual(result, -1)
555 self.assertIn("--computer-tgt-lifetime-mins must be between 45 and 2147483647",
558 # check upper bounds (2147483647)
559 result, out, err = self.runcmd("domain", "auth", "policy", "modify",
560 "--name", name + "Upper",
561 "--computer-tgt-lifetime-mins", "2147483648")
562 self.assertEqual(result, -1)
563 self.assertIn("--computer-tgt-lifetime-mins must be between 45 and 2147483647",
566 def test_modify__user_allowed_to_authenticate_from(self):
567 """Modify authentication policy user allowed to authenticate from."""
568 name = self.unique_name()
569 expected = "O:SYG:SYD:(XA;OICI;CR;;;WD;(Member_of {SID(AO)}))"
571 # Create a policy to modify for this test.
572 self.addCleanup(self.delete_authentication_policy, name=name, force=True)
573 self.runcmd("domain", "auth", "policy", "create", "--name", name)
575 # Modify user allowed to authenticate from field
576 result, out, err = self.runcmd("domain", "auth", "policy", "modify",
578 "--user-allowed-to-authenticate-from",
580 self.assertIsNone(result, msg=err)
582 # Check user allowed to authenticate from field was modified.
583 policy = self.get_authentication_policy(name)
584 self.assertEqual(str(policy["cn"]), name)
585 desc = policy["msDS-UserAllowedToAuthenticateFrom"][0]
586 sddl = ndr_unpack(security.descriptor, desc).as_sddl()
587 self.assertEqual(sddl, expected)
589 def test_modify__user_allowed_to_authenticate_from_silo(self):
590 """Test the --user-allowed-to-authenticate-from-silo shortcut."""
591 name = self.unique_name()
593 # Create a policy to modify for this test.
594 self.addCleanup(self.delete_authentication_policy, name=name, force=True)
595 self.runcmd("domain", "auth", "policy", "create", "--name", name)
597 # Modify user allowed to authenticate from silo field
598 result, out, err = self.runcmd("domain", "auth", "policy", "modify",
600 "--user-allowed-to-authenticate-from-silo",
602 self.assertIsNone(result, msg=err)
604 # Check generated SDDL.
605 policy = self.get_authentication_policy(name)
606 desc = policy["msDS-UserAllowedToAuthenticateFrom"][0]
607 sddl = ndr_unpack(security.descriptor, desc).as_sddl()
610 "O:SYG:SYD:(XA;OICI;CR;;;WD;(@USER.ad://ext/AuthenticationSilo/QA))")
612 def test_modify__user_allowed_to_authenticate_to(self):
613 """Modify authentication policy user allowed to authenticate to."""
614 name = self.unique_name()
615 expected = "O:SYG:SYD:(XA;OICI;CR;;;WD;(Member_of {SID(AO)}))"
617 # Create a policy to modify for this test.
618 self.addCleanup(self.delete_authentication_policy, name=name, force=True)
619 self.runcmd("domain", "auth", "policy", "create", "--name", name)
621 # Modify user allowed to authenticate to field
622 result, out, err = self.runcmd("domain", "auth", "policy", "modify",
624 "--user-allowed-to-authenticate-to",
626 self.assertIsNone(result, msg=err)
628 # Check user allowed to authenticate to field was modified.
629 policy = self.get_authentication_policy(name)
630 self.assertEqual(str(policy["cn"]), name)
631 desc = policy["msDS-UserAllowedToAuthenticateTo"][0]
632 sddl = ndr_unpack(security.descriptor, desc).as_sddl()
633 self.assertEqual(sddl, expected)
635 def test_modify__service_allowed_to_authenticate_from(self):
636 """Modify authentication policy service allowed to authenticate from."""
637 name = self.unique_name()
638 expected = "O:SYG:SYD:(XA;OICI;CR;;;WD;(Member_of {SID(AO)}))"
640 # Create a policy to modify for this test.
641 self.addCleanup(self.delete_authentication_policy, name=name, force=True)
642 self.runcmd("domain", "auth", "policy", "create", "--name", name)
644 # Modify service allowed to authenticate from field
645 result, out, err = self.runcmd("domain", "auth", "policy", "modify",
647 "--service-allowed-to-authenticate-from",
649 self.assertIsNone(result, msg=err)
651 # Check service allowed to authenticate from field was modified.
652 policy = self.get_authentication_policy(name)
653 self.assertEqual(str(policy["cn"]), name)
654 desc = policy["msDS-ServiceAllowedToAuthenticateFrom"][0]
655 sddl = ndr_unpack(security.descriptor, desc).as_sddl()
656 self.assertEqual(sddl, expected)
658 def test_modify__service_allowed_to_authenticate_from_silo(self):
659 """Test the --service-allowed-to-authenticate-from-silo shortcut."""
660 name = self.unique_name()
662 # Create a policy to modify for this test.
663 self.addCleanup(self.delete_authentication_policy, name=name, force=True)
664 self.runcmd("domain", "auth", "policy", "create", "--name", name)
666 # Modify user allowed to authenticate from silo field
667 result, out, err = self.runcmd("domain", "auth", "policy", "modify",
669 "--service-allowed-to-authenticate-from-silo",
671 self.assertIsNone(result, msg=err)
673 # Check generated SDDL.
674 policy = self.get_authentication_policy(name)
675 desc = policy["msDS-ServiceAllowedToAuthenticateFrom"][0]
676 sddl = ndr_unpack(security.descriptor, desc).as_sddl()
679 "O:SYG:SYD:(XA;OICI;CR;;;WD;(@USER.ad://ext/AuthenticationSilo/Developers))")
681 def test_modify__service_allowed_to_authenticate_to(self):
682 """Modify authentication policy service allowed to authenticate to."""
683 name = self.unique_name()
684 expected = "O:SYG:SYD:(XA;OICI;CR;;;WD;(Member_of {SID(AO)}))"
686 # Create a policy to modify for this test.
687 self.addCleanup(self.delete_authentication_policy, name=name, force=True)
688 self.runcmd("domain", "auth", "policy", "create", "--name", name)
690 # Modify service allowed to authenticate to field
691 result, out, err = self.runcmd("domain", "auth", "policy", "modify",
693 "--service-allowed-to-authenticate-to",
695 self.assertIsNone(result, msg=err)
697 # Check service allowed to authenticate to field was modified.
698 policy = self.get_authentication_policy(name)
699 self.assertEqual(str(policy["cn"]), name)
700 desc = policy["msDS-ServiceAllowedToAuthenticateTo"][0]
701 sddl = ndr_unpack(security.descriptor, desc).as_sddl()
702 self.assertEqual(sddl, expected)
704 def test_modify__computer_allowed_to_authenticate_to(self):
705 """Modify authentication policy computer allowed to authenticate to."""
706 name = self.unique_name()
707 expected = "O:SYG:SYD:(XA;OICI;CR;;;WD;(Member_of {SID(AO)}))"
709 # Create a policy to modify for this test.
710 self.addCleanup(self.delete_authentication_policy, name=name, force=True)
711 self.runcmd("domain", "auth", "policy", "create", "--name", name)
713 # Modify computer allowed to authenticate to field
714 result, out, err = self.runcmd("domain", "auth", "policy", "modify",
716 "--computer-allowed-to-authenticate-to",
718 self.assertIsNone(result, msg=err)
720 # Check computer allowed to authenticate to field was modified.
721 policy = self.get_authentication_policy(name)
722 self.assertEqual(str(policy["cn"]), name)
723 desc = policy["msDS-ComputerAllowedToAuthenticateTo"][0]
724 sddl = ndr_unpack(security.descriptor, desc).as_sddl()
725 self.assertEqual(sddl, expected)
727 def test_modify__name_missing(self):
728 """Test modify authentication but the --name argument is missing."""
729 result, out, err = self.runcmd("domain", "auth", "policy", "modify",
730 "--description", "NewDescription")
731 self.assertEqual(result, -1)
732 self.assertIn("Argument --name is required.", err)
734 def test_modify__notfound(self):
735 """Test modify an authentication silo that doesn't exist."""
736 result, out, err = self.runcmd("domain", "auth", "policy", "modify",
737 "--name", "doesNotExist",
738 "--description", "NewDescription")
739 self.assertEqual(result, -1)
740 self.assertIn("Authentication policy doesNotExist not found.", err)
742 def test_modify__audit_enforce(self):
743 """Test modify authentication policy using --audit and --enforce."""
744 name = self.unique_name()
746 # Create a policy to modify for this test.
747 self.addCleanup(self.delete_authentication_policy,
748 name=name, force=True)
749 self.runcmd("domain", "auth", "policy", "create", "--name", name)
751 # Change to audit, the default is --enforce.
752 result, out, err = self.runcmd("domain", "auth", "policy", "modify",
755 self.assertIsNone(result, msg=err)
757 # Check that the policy was changed to --audit.
758 policy = self.get_authentication_policy(name)
759 self.assertEqual(str(policy["msDS-AuthNPolicyEnforced"]), "FALSE")
761 result, out, err = self.runcmd("domain", "auth", "policy", "modify",
764 self.assertIsNone(result, msg=err)
766 # Check if the policy was changed back to --enforce.
767 policy = self.get_authentication_policy(name)
768 self.assertEqual(str(policy["msDS-AuthNPolicyEnforced"]), "TRUE")
770 def test_modify__protect_unprotect(self):
771 """Test modify authentication policy using --protect and --unprotect."""
772 name = self.unique_name()
774 # Create a policy to modify for this test.
775 self.addCleanup(self.delete_authentication_policy, name=name, force=True)
776 self.runcmd("domain", "auth", "policy", "create", "--name", name)
778 utils = SDUtils(self.samdb)
779 result, out, err = self.runcmd("domain", "auth", "policy", "modify",
782 self.assertIsNone(result, msg=err)
784 # Check that claim type was protected.
785 policy = self.get_authentication_policy(name)
786 desc = utils.get_sd_as_sddl(policy["dn"])
787 self.assertIn("(D;;DTSD;;;WD)", desc)
789 result, out, err = self.runcmd("domain", "auth", "policy", "modify",
792 self.assertIsNone(result, msg=err)
794 # Check that claim type was unprotected.
795 policy = self.get_authentication_policy(name)
796 desc = utils.get_sd_as_sddl(policy["dn"])
797 self.assertNotIn("(D;;DTSD;;;WD)", desc)
799 def test_modify__audit_enforce_together(self):
800 """Test modify auth policy using both --audit and --enforce."""
801 result, out, err = self.runcmd("domain", "auth", "policy", "modify",
802 "--name", "User Policy",
803 "--audit", "--enforce")
804 self.assertEqual(result, -1)
805 self.assertIn("--audit and --enforce cannot be used together.", err)
807 def test_modify__protect_unprotect_together(self):
808 """Test modify authentication policy using --protect and --unprotect."""
809 result, out, err = self.runcmd("domain", "auth", "policy", "modify",
810 "--name", "User Policy",
811 "--protect", "--unprotect")
812 self.assertEqual(result, -1)
813 self.assertIn("--protect and --unprotect cannot be used together.", err)
815 def test_modify__fails(self):
816 """Test modifying an authentication policy, but it fails."""
817 # Raise ModelError when ldb.add() is called.
818 with patch.object(SamDB, "modify") as modify_mock:
819 modify_mock.side_effect = ModelError("Custom error message")
820 result, out, err = self.runcmd("domain", "auth", "policy", "modify",
821 "--name", "User Policy",
822 "--description", "New description")
823 self.assertEqual(result, -1)
824 self.assertIn("Custom error message", err)
826 def test_delete__success(self):
827 """Test deleting an authentication policy that is not protected."""
828 # Create non-protected authentication policy.
829 result, out, err = self.runcmd("domain", "auth", "policy", "create",
831 self.assertIsNone(result, msg=err)
832 policy = self.get_authentication_policy("deleteTest")
833 self.assertIsNotNone(policy)
836 result, out, err = self.runcmd("domain", "auth", "policy", "delete",
837 "--name", "deleteTest")
838 self.assertIsNone(result, msg=err)
840 # Authentication policy shouldn't exist anymore.
841 policy = self.get_authentication_policy("deleteTest")
842 self.assertIsNone(policy)
844 def test_delete__protected(self):
845 """Test deleting a protected auth policy, with and without --force."""
846 # Create protected authentication policy.
847 result, out, err = self.runcmd("domain", "auth", "policy", "create",
848 "--name=deleteProtected",
850 self.assertIsNone(result, msg=err)
851 policy = self.get_authentication_policy("deleteProtected")
852 self.assertIsNotNone(policy)
855 result, out, err = self.runcmd("domain", "auth", "policy", "delete",
856 "--name=deleteProtected")
857 self.assertEqual(result, -1)
859 # Authentication silo should still exist.
860 policy = self.get_authentication_policy("deleteProtected")
861 self.assertIsNotNone(policy)
863 # Try a force delete instead.
864 result, out, err = self.runcmd("domain", "auth", "policy", "delete",
865 "--name=deleteProtected", "--force")
866 self.assertIsNone(result, msg=err)
868 # Authentication silo shouldn't exist anymore.
869 policy = self.get_authentication_policy("deleteProtected")
870 self.assertIsNone(policy)
872 def test_delete__notfound(self):
873 """Test deleting an authentication policy that doesn't exist."""
874 result, out, err = self.runcmd("domain", "auth", "policy", "delete",
875 "--name", "doesNotExist")
876 self.assertEqual(result, -1)
877 self.assertIn("Authentication policy doesNotExist not found.", err)
879 def test_delete__name_required(self):
880 """Test deleting an authentication policy without --name argument."""
881 result, out, err = self.runcmd("domain", "auth", "policy", "delete")
882 self.assertEqual(result, -1)
883 self.assertIn("Argument --name is required.", err)
885 def test_delete__force_fails(self):
886 """Test deleting an authentication policy with --force, but it fails."""
887 name = self.unique_name()
889 # Create protected authentication policy.
890 self.addCleanup(self.delete_authentication_policy, name=name, force=True)
891 result, out, err = self.runcmd("domain", "auth", "policy", "create",
894 self.assertIsNone(result, msg=err)
897 policy = self.get_authentication_policy(name)
898 self.assertIsNotNone(policy)
900 # Try doing delete with --force.
901 # Patch SDUtils.dacl_delete_aces with a Mock that raises ModelError.
902 with patch.object(SDUtils, "dacl_delete_aces") as delete_mock:
903 delete_mock.side_effect = ModelError("Custom error message")
904 result, out, err = self.runcmd("domain", "auth", "policy", "delete",
907 self.assertEqual(result, -1)
908 self.assertIn("Custom error message", err)
910 def test_delete__fails(self):
911 """Test deleting an authentication policy, but it fails."""
912 name = self.unique_name()
914 # Create regular authentication policy.
915 self.addCleanup(self.delete_authentication_policy, name=name, force=True)
916 result, out, err = self.runcmd("domain", "auth", "policy", "create",
918 self.assertIsNone(result, msg=err)
921 policy = self.get_authentication_policy(name)
922 self.assertIsNotNone(policy)
924 # Raise ModelError when ldb.delete() is called.
925 with patch.object(SamDB, "delete") as delete_mock:
926 delete_mock.side_effect = ModelError("Custom error message")
927 result, out, err = self.runcmd("domain", "auth", "policy", "delete",
929 self.assertEqual(result, -1)
930 self.assertIn("Custom error message", err)
932 # When not using --force we get a hint.
933 self.assertIn("Try --force", err)
935 def test_delete__protected_fails(self):
936 """Test deleting an authentication policy, but it fails."""
937 name = self.unique_name()
939 # Create protected authentication policy.
940 self.addCleanup(self.delete_authentication_policy, name=name, force=True)
941 result, out, err = self.runcmd("domain", "auth", "policy", "create",
944 self.assertIsNone(result, msg=err)
947 policy = self.get_authentication_policy(name)
948 self.assertIsNotNone(policy)
950 # Raise ModelError when ldb.delete() is called.
951 with patch.object(SamDB, "delete") as delete_mock:
952 delete_mock.side_effect = ModelError("Custom error message")
953 result, out, err = self.runcmd("domain", "auth", "policy", "delete",
956 self.assertEqual(result, -1)
957 self.assertIn("Custom error message", err)
959 # When using --force we don't get the hint.
960 self.assertNotIn("Try --force", err)