python: silos: add support for allowed to authenticate from silo shortcut
[amitay/samba-autobuild/.git] / python / samba / tests / samba_tool / domain_auth_policy.py
1 # Unix SMB/CIFS implementation.
2 #
3 # Tests for samba-tool domain auth policy command
4 #
5 # Copyright (C) Catalyst.Net Ltd. 2023
6 #
7 # Written by Rob van der Linde <rob@catalyst.net.nz>
8 #
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.
13 #
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.
18 #
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/>.
21 #
22
23 import json
24 from optparse import OptionValueError
25 from unittest.mock import patch
26
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
32
33 from .domain_auth_base import BaseAuthCmdTest
34
35
36 class AuthPolicyCmdTestCase(BaseAuthCmdTest):
37
38     def test_list(self):
39         """Test listing authentication policies in list format."""
40         result, out, err = self.runcmd("domain", "auth", "policy", "list")
41         self.assertIsNone(result, msg=err)
42
43         expected_policies = ["User Policy", "Service Policy", "Computer Policy"]
44
45         for policy in expected_policies:
46             self.assertIn(policy, out)
47
48     def test_list__json(self):
49         """Test listing authentication policies in JSON format."""
50         result, out, err = self.runcmd("domain", "auth", "policy",
51                                        "list", "--json")
52         self.assertIsNone(result, msg=err)
53
54         # we should get valid json
55         policies = json.loads(out)
56
57         expected_policies = ["User Policy", "Service Policy", "Computer Policy"]
58
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)
66
67     def test_view(self):
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)
72
73         # we should get valid json
74         policy = json.loads(out)
75
76         # check a few fields only
77         self.assertEqual(policy["cn"], "User Policy")
78         self.assertEqual(policy["msDS-AuthNPolicyEnforced"], True)
79
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)
86
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)
92
93     def test_create__success(self):
94         """Test creating a new authentication policy."""
95         name = self.unique_name()
96
97         self.addCleanup(self.delete_authentication_policy, name=name, force=True)
98         result, out, err = self.runcmd("domain", "auth", "policy", "create",
99                                        "--name", name)
100         self.assertIsNone(result, msg=err)
101
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")
106
107     def test_create__description(self):
108         """Test creating a new authentication policy with description set."""
109         name = self.unique_name()
110
111         self.addCleanup(self.delete_authentication_policy, name=name, force=True)
112         result, out, err = self.runcmd("domain", "auth", "policy", "create",
113                                        "--name", name,
114                                        "--description", "Custom Description")
115         self.assertIsNone(result, msg=err)
116
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")
121
122     def test_create__user_tgt_lifetime_mins(self):
123         """Test create a new authentication policy with --user-tgt-lifetime-mins.
124
125         Also checks the upper and lower bounds are handled.
126         """
127         name = self.unique_name()
128
129         self.addCleanup(self.delete_authentication_policy, name=name, force=True)
130         result, out, err = self.runcmd("domain", "auth", "policy", "create",
131                                        "--name", name,
132                                        "--user-tgt-lifetime-mins", "60")
133         self.assertIsNone(result, msg=err)
134
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")
139
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",
146                       err)
147
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",
154                       err)
155
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()
159
160         self.addCleanup(self.delete_authentication_policy, name=name, force=True)
161         result, out, err = self.runcmd("domain", "auth", "policy", "create",
162                                        "--name", name,
163                                        "--user-allowed-to-authenticate-from-silo",
164                                        "Developers")
165         self.assertIsNone(result, msg=err)
166
167         # Check policy fields.
168         policy = self.get_authentication_policy(name)
169         self.assertEqual(str(policy["cn"]), name)
170
171         # Check generated SDDL.
172         desc = policy["msDS-UserAllowedToAuthenticateFrom"][0]
173         sddl = ndr_unpack(security.descriptor, desc).as_sddl()
174         self.assertEqual(
175             sddl,
176             "O:SYG:SYD:(XA;OICI;CR;;;WD;(@USER.ad://ext/AuthenticationSilo/Developers))")
177
178     def test_create__service_tgt_lifetime_mins(self):
179         """Test create a new authentication policy with --service-tgt-lifetime-mins.
180
181         Also checks the upper and lower bounds are handled.
182         """
183         name = self.unique_name()
184
185         self.addCleanup(self.delete_authentication_policy, name=name, force=True)
186         result, out, err = self.runcmd("domain", "auth", "policy", "create",
187                                        "--name", name,
188                                        "--service-tgt-lifetime-mins", "60")
189         self.assertIsNone(result, msg=err)
190
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")
195
196         # check lower bounds (45)
197         result, out, err = self.runcmd("domain", "auth", "policy", "create",
198                                        "--name", name,
199                                        "--service-tgt-lifetime-mins", "44")
200         self.assertEqual(result, -1)
201         self.assertIn("--service-tgt-lifetime-mins must be between 45 and 2147483647",
202                       err)
203
204         # check upper bounds (2147483647)
205         result, out, err = self.runcmd("domain", "auth", "policy", "create",
206                                        "--name", name,
207                                        "--service-tgt-lifetime-mins", "2147483648")
208         self.assertEqual(result, -1)
209         self.assertIn("--service-tgt-lifetime-mins must be between 45 and 2147483647",
210                       err)
211
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()
215
216         self.addCleanup(self.delete_authentication_policy, name=name, force=True)
217         result, out, err = self.runcmd("domain", "auth", "policy", "create",
218                                        "--name", name,
219                                        "--service-allowed-to-authenticate-from-silo",
220                                        "Managers")
221         self.assertIsNone(result, msg=err)
222
223         # Check policy fields.
224         policy = self.get_authentication_policy(name)
225         self.assertEqual(str(policy["cn"]), name)
226         desc = policy["msDS-ServiceAllowedToAuthenticateFrom"][0]
227
228         # Check generated SDDL.
229         sddl = ndr_unpack(security.descriptor, desc).as_sddl()
230         self.assertEqual(
231             sddl,
232             "O:SYG:SYD:(XA;OICI;CR;;;WD;(@USER.ad://ext/AuthenticationSilo/Managers))")
233
234     def test_create__computer_tgt_lifetime_mins(self):
235         """Test create a new authentication policy with --computer-tgt-lifetime-mins.
236
237         Also checks the upper and lower bounds are handled.
238         """
239         name = self.unique_name()
240
241         self.addCleanup(self.delete_authentication_policy, name=name, force=True)
242         result, out, err = self.runcmd("domain", "auth", "policy", "create",
243                                        "--name", name,
244                                        "--computer-tgt-lifetime-mins", "60")
245         self.assertIsNone(result, msg=err)
246
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")
251
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",
258                       err)
259
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",
266                       err)
267
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)}))"
272
273         self.addCleanup(self.delete_authentication_policy, name=name, force=True)
274         result, out, err = self.runcmd("domain", "auth", "policy", "create",
275                                        "--name", name,
276                                        "--user-allowed-to-authenticate-from",
277                                        expected)
278         self.assertIsNone(result, msg=err)
279
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)
286
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()
290
291         result, out, err = self.runcmd("domain", "auth", "policy", "create",
292                                        "--name", name,
293                                        "--user-allowed-to-authenticate-from",
294                                        "*INVALID SDDL*")
295
296         self.assertEqual(result, -1)
297         self.assertIn(
298             "msDS-UserAllowedToAuthenticateFrom: Unable to parse SDDL", err)
299
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)
306
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)
312
313     def test_create__audit(self):
314         """Test create authentication policy with --audit flag."""
315         name = self.unique_name()
316
317         self.addCleanup(self.delete_authentication_policy, name=name, force=True)
318         result, out, err = self.runcmd("domain", "auth", "policy", "create",
319                                        "--name", name,
320                                        "--audit")
321         self.assertIsNone(result, msg=err)
322
323         # fetch and check policy
324         policy = self.get_authentication_policy(name)
325         self.assertEqual(str(policy["msDS-AuthNPolicyEnforced"]), "FALSE")
326
327     def test_create__enforce(self):
328         """Test create authentication policy with --enforce flag."""
329         name = self.unique_name()
330
331         self.addCleanup(self.delete_authentication_policy, name=name, force=True)
332         result, out, err = self.runcmd("domain", "auth", "policy", "create",
333                                        "--name", name,
334                                        "--enforce")
335         self.assertIsNone(result, msg=err)
336
337         # fetch and check policy
338         policy = self.get_authentication_policy(name)
339         self.assertEqual(str(policy["msDS-AuthNPolicyEnforced"]), "TRUE")
340
341     def test_create__audit_enforce_together(self):
342         """Test create auth policy using both --audit and --enforce."""
343         name = self.unique_name()
344
345         result, out, err = self.runcmd("domain", "auth", "policy", "create",
346                                        "--name", name,
347                                        "--audit", "--enforce")
348
349         self.assertEqual(result, -1)
350         self.assertIn("--audit and --enforce cannot be used together.", err)
351
352     def test_create__protect_unprotect_together(self):
353         """Test create authentication policy using --protect and --unprotect."""
354         name = self.unique_name()
355
356         result, out, err = self.runcmd("domain", "auth", "policy", "create",
357                                        "--name", name,
358                                        "--protect", "--unprotect")
359
360         self.assertEqual(result, -1)
361         self.assertIn("--protect and --unprotect cannot be used together.", err)
362
363     def test_create__user_allowed_to_authenticate_from_repeated(self):
364         """Test repeating similar arguments doesn't make sense to use together.
365
366         --user-allowed-to-authenticate-from
367         --user-allowed-to-authenticate-from-silo
368         """
369         sddl = "O:SYG:SYD:(XA;OICI;CR;;;WD;(@USER.ad://ext/AuthenticationSilo/Developers))"
370         name = self.unique_name()
371
372         result, out, err = self.runcmd("domain", "auth", "policy", "create",
373                                        "--name", name,
374                                        "--user-allowed-to-authenticate-from",
375                                        sddl,
376                                        "--user-allowed-to-authenticate-from-silo",
377                                        "Managers")
378
379         self.assertEqual(result, -1)
380         self.assertIn("--user-allowed-to-authenticate-from argument repeated 2 times.", err)
381
382     def test_create__service_allowed_to_authenticate_from_repeated(self):
383         """Test repeating similar arguments doesn't make sense to use together.
384
385         --service-allowed-to-authenticate-from
386         --service-allowed-to-authenticate-from-silo
387         """
388         sddl = "O:SYG:SYD:(XA;OICI;CR;;;WD;(@USER.ad://ext/AuthenticationSilo/Managers))"
389         name = self.unique_name()
390
391         result, out, err = self.runcmd("domain", "auth", "policy", "create",
392                                        "--name", name,
393                                        "--service-allowed-to-authenticate-from",
394                                        sddl,
395                                        "--service-allowed-to-authenticate-from-silo",
396                                        "QA")
397
398         self.assertEqual(result, -1)
399         self.assertIn("--service-allowed-to-authenticate-from argument repeated 2 times.", err)
400
401     def test_create__fails(self):
402         """Test creating an authentication policy, but it fails."""
403         name = self.unique_name()
404
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",
409                                            "--name", name)
410             self.assertEqual(result, -1)
411             self.assertIn("Custom error message", err)
412
413     def test_modify__description(self):
414         """Test modifying an authentication policy description."""
415         name = self.unique_name()
416
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)
420
421         # Change the policy description.
422         result, out, err = self.runcmd("domain", "auth", "policy", "modify",
423                                        "--name", name,
424                                        "--description", "NewDescription")
425         self.assertIsNone(result, msg=err)
426
427         # Verify fields were changed.
428         policy = self.get_authentication_policy(name)
429         self.assertEqual(str(policy["description"]), "NewDescription")
430
431     def test_modify__strong_ntlm_policy(self):
432         """Test modify strong ntlm policy on the authentication policy."""
433         name = self.unique_name()
434
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)
438
439         result, out, err = self.runcmd("domain", "auth", "policy", "modify",
440                                        "--name", name,
441                                        "--strong-ntlm-policy", "Required")
442         self.assertIsNone(result, msg=err)
443
444         # Verify fields were changed.
445         policy = self.get_authentication_policy(name)
446         self.assertEqual(str(policy["msDS-StrongNTLMPolicy"]), "2")
447
448         # Check an invalid choice.
449         with self.assertRaises((OptionValueError, SystemExit)):
450             self.runcmd("domain", "auth", "policy", "modify",
451                         "--name", name,
452                         "--strong-ntlm-policy", "Invalid")
453
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).
457
458     def test_modify__user_tgt_lifetime_mins(self):
459         """Test modifying an authentication policy --user-tgt-lifetime-mins.
460
461         This includes checking the upper and lower bounds.
462         """
463         name = self.unique_name()
464
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)
468
469         result, out, err = self.runcmd("domain", "auth", "policy", "modify",
470                                        "--name", name,
471                                        "--user-tgt-lifetime-mins", "120")
472         self.assertIsNone(result, msg=err)
473
474         # Verify field was changed.
475         policy = self.get_authentication_policy(name)
476         self.assertEqual(str(policy["msDS-UserTGTLifetime"]), "120")
477
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",
484                       err)
485
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",
492                       err)
493
494     def test_modify__service_tgt_lifetime_mins(self):
495         """Test modifying an authentication policy --service-tgt-lifetime-mins.
496
497         This includes checking the upper and lower bounds.
498         """
499         name = self.unique_name()
500
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)
504
505         result, out, err = self.runcmd("domain", "auth", "policy", "modify",
506                                        "--name", name,
507                                        "--service-tgt-lifetime-mins", "120")
508         self.assertIsNone(result, msg=err)
509
510         # Verify field was changed.
511         policy = self.get_authentication_policy(name)
512         self.assertEqual(str(policy["msDS-ServiceTGTLifetime"]), "120")
513
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",
520                       err)
521
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",
528                       err)
529
530     def test_modify__computer_tgt_lifetime_mins(self):
531         """Test modifying an authentication policy --computer-tgt-lifetime-mins.
532
533         This includes checking the upper and lower bounds.
534         """
535         name = self.unique_name()
536
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)
540
541         result, out, err = self.runcmd("domain", "auth", "policy", "modify",
542                                        "--name", name,
543                                        "--computer-tgt-lifetime-mins", "120")
544         self.assertIsNone(result, msg=err)
545
546         # Verify field was changed.
547         policy = self.get_authentication_policy(name)
548         self.assertEqual(str(policy["msDS-ComputerTGTLifetime"]), "120")
549
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",
556                       err)
557
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",
564                       err)
565
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)}))"
570
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)
574
575         # Modify user allowed to authenticate from field
576         result, out, err = self.runcmd("domain", "auth", "policy", "modify",
577                                        "--name", name,
578                                        "--user-allowed-to-authenticate-from",
579                                        expected)
580         self.assertIsNone(result, msg=err)
581
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)
588
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()
592
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)
596
597         # Modify user allowed to authenticate from silo field
598         result, out, err = self.runcmd("domain", "auth", "policy", "modify",
599                                        "--name", name,
600                                        "--user-allowed-to-authenticate-from-silo",
601                                        "QA")
602         self.assertIsNone(result, msg=err)
603
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()
608         self.assertEqual(
609             sddl,
610             "O:SYG:SYD:(XA;OICI;CR;;;WD;(@USER.ad://ext/AuthenticationSilo/QA))")
611
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)}))"
616
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)
620
621         # Modify user allowed to authenticate to field
622         result, out, err = self.runcmd("domain", "auth", "policy", "modify",
623                                        "--name", name,
624                                        "--user-allowed-to-authenticate-to",
625                                        expected)
626         self.assertIsNone(result, msg=err)
627
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)
634
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)}))"
639
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)
643
644         # Modify service allowed to authenticate from field
645         result, out, err = self.runcmd("domain", "auth", "policy", "modify",
646                                        "--name", name,
647                                        "--service-allowed-to-authenticate-from",
648                                        expected)
649         self.assertIsNone(result, msg=err)
650
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)
657
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()
661
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)
665
666         # Modify user allowed to authenticate from silo field
667         result, out, err = self.runcmd("domain", "auth", "policy", "modify",
668                                        "--name", name,
669                                        "--service-allowed-to-authenticate-from-silo",
670                                        "Developers")
671         self.assertIsNone(result, msg=err)
672
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()
677         self.assertEqual(
678             sddl,
679             "O:SYG:SYD:(XA;OICI;CR;;;WD;(@USER.ad://ext/AuthenticationSilo/Developers))")
680
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)}))"
685
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)
689
690         # Modify service allowed to authenticate to field
691         result, out, err = self.runcmd("domain", "auth", "policy", "modify",
692                                        "--name", name,
693                                        "--service-allowed-to-authenticate-to",
694                                        expected)
695         self.assertIsNone(result, msg=err)
696
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)
703
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)}))"
708
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)
712
713         # Modify computer allowed to authenticate to field
714         result, out, err = self.runcmd("domain", "auth", "policy", "modify",
715                                        "--name", name,
716                                        "--computer-allowed-to-authenticate-to",
717                                        expected)
718         self.assertIsNone(result, msg=err)
719
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)
726
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)
733
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)
741
742     def test_modify__audit_enforce(self):
743         """Test modify authentication policy using --audit and --enforce."""
744         name = self.unique_name()
745
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)
750
751         # Change to audit, the default is --enforce.
752         result, out, err = self.runcmd("domain", "auth", "policy", "modify",
753                                        "--name", name,
754                                        "--audit")
755         self.assertIsNone(result, msg=err)
756
757         # Check that the policy was changed to --audit.
758         policy = self.get_authentication_policy(name)
759         self.assertEqual(str(policy["msDS-AuthNPolicyEnforced"]), "FALSE")
760
761         result, out, err = self.runcmd("domain", "auth", "policy", "modify",
762                                        "--name", name,
763                                        "--enforce")
764         self.assertIsNone(result, msg=err)
765
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")
769
770     def test_modify__protect_unprotect(self):
771         """Test modify authentication policy using --protect and --unprotect."""
772         name = self.unique_name()
773
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)
777
778         utils = SDUtils(self.samdb)
779         result, out, err = self.runcmd("domain", "auth", "policy", "modify",
780                                        "--name", name,
781                                        "--protect")
782         self.assertIsNone(result, msg=err)
783
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)
788
789         result, out, err = self.runcmd("domain", "auth", "policy", "modify",
790                                        "--name", name,
791                                        "--unprotect")
792         self.assertIsNone(result, msg=err)
793
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)
798
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)
806
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)
814
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)
825
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",
830                                        "--name=deleteTest")
831         self.assertIsNone(result, msg=err)
832         policy = self.get_authentication_policy("deleteTest")
833         self.assertIsNotNone(policy)
834
835         # Do the deletion.
836         result, out, err = self.runcmd("domain", "auth", "policy", "delete",
837                                        "--name", "deleteTest")
838         self.assertIsNone(result, msg=err)
839
840         # Authentication policy shouldn't exist anymore.
841         policy = self.get_authentication_policy("deleteTest")
842         self.assertIsNone(policy)
843
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",
849                                        "--protect")
850         self.assertIsNone(result, msg=err)
851         policy = self.get_authentication_policy("deleteProtected")
852         self.assertIsNotNone(policy)
853
854         # Do the deletion.
855         result, out, err = self.runcmd("domain", "auth", "policy", "delete",
856                                        "--name=deleteProtected")
857         self.assertEqual(result, -1)
858
859         # Authentication silo should still exist.
860         policy = self.get_authentication_policy("deleteProtected")
861         self.assertIsNotNone(policy)
862
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)
867
868         # Authentication silo shouldn't exist anymore.
869         policy = self.get_authentication_policy("deleteProtected")
870         self.assertIsNone(policy)
871
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)
878
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)
884
885     def test_delete__force_fails(self):
886         """Test deleting an authentication policy with --force, but it fails."""
887         name = self.unique_name()
888
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",
892                                        "--name", name,
893                                        "--protect")
894         self.assertIsNone(result, msg=err)
895
896         # Policy exists
897         policy = self.get_authentication_policy(name)
898         self.assertIsNotNone(policy)
899
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",
905                                            "--name", name,
906                                            "--force")
907             self.assertEqual(result, -1)
908             self.assertIn("Custom error message", err)
909
910     def test_delete__fails(self):
911         """Test deleting an authentication policy, but it fails."""
912         name = self.unique_name()
913
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",
917                                        "--name", name)
918         self.assertIsNone(result, msg=err)
919
920         # Policy exists
921         policy = self.get_authentication_policy(name)
922         self.assertIsNotNone(policy)
923
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",
928                                            "--name", name)
929             self.assertEqual(result, -1)
930             self.assertIn("Custom error message", err)
931
932             # When not using --force we get a hint.
933             self.assertIn("Try --force", err)
934
935     def test_delete__protected_fails(self):
936         """Test deleting an authentication policy, but it fails."""
937         name = self.unique_name()
938
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",
942                                        "--name", name,
943                                        "--protect")
944         self.assertIsNone(result, msg=err)
945
946         # Policy exists
947         policy = self.get_authentication_policy(name)
948         self.assertIsNotNone(policy)
949
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",
954                                            "--name", name,
955                                            "--force")
956             self.assertEqual(result, -1)
957             self.assertIn("Custom error message", err)
958
959             # When using --force we don't get the hint.
960             self.assertNotIn("Try --force", err)