ctdb-server: Use find_public_ip_vnn() in a couple of extra places
[Samba.git] / python / samba / tests / samba_tool / domain_auth_silo.py
blob2659c15c9affc37b86de5bf38adc7b6b83029216
1 # Unix SMB/CIFS implementation.
3 # Tests for samba-tool domain auth silo 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/>.
23 import json
24 from unittest.mock import patch
26 from samba.domain.models.exceptions import ModelError
27 from samba.samdb import SamDB
28 from samba.sd_utils import SDUtils
30 from .silo_base import SiloTest
33 class AuthSiloCmdTestCase(SiloTest):
35 def test_list(self):
36 """Test listing authentication silos in list format."""
37 result, out, err = self.runcmd("domain", "auth", "silo", "list")
38 self.assertIsNone(result, msg=err)
40 expected_silos = ["Developers", "Managers", "QA"]
42 for silo in expected_silos:
43 self.assertIn(silo, out)
45 def test_list___json(self):
46 """Test listing authentication silos in JSON format."""
47 result, out, err = self.runcmd("domain", "auth", "silo",
48 "list", "--json")
49 self.assertIsNone(result, msg=err)
51 # we should get valid json
52 silos = json.loads(out)
54 expected_silos = ["Developers", "Managers", "QA"]
56 for name in expected_silos:
57 silo = silos[name]
58 self.assertIn("msDS-AuthNPolicySilo", list(silo["objectClass"]))
59 self.assertIn("description", silo)
60 self.assertIn("msDS-UserAuthNPolicy", silo)
61 self.assertIn("objectGUID", silo)
63 def test_view(self):
64 """Test viewing a single authentication silo."""
65 result, out, err = self.runcmd("domain", "auth", "silo", "view",
66 "--name", "Developers")
67 self.assertIsNone(result, msg=err)
69 # we should get valid json
70 silo = json.loads(out)
72 # check a few fields only
73 self.assertEqual(silo["cn"], "Developers")
74 self.assertEqual(silo["description"],
75 "Developers, Developers, Developers!")
77 def test_view__notfound(self):
78 """Test viewing an authentication silo that doesn't exist."""
79 result, out, err = self.runcmd("domain", "auth", "silo", "view",
80 "--name", "doesNotExist")
81 self.assertEqual(result, -1)
82 self.assertIn("Authentication silo doesNotExist not found.", err)
84 def test_view__name_required(self):
85 """Test view authentication silo without --name argument."""
86 result, out, err = self.runcmd("domain", "auth", "silo", "view")
87 self.assertEqual(result, -1)
88 self.assertIn("Argument --name is required.", err)
90 def test_create__single_policy(self):
91 """Test creating a new authentication silo with a single policy."""
92 name = self.unique_name()
94 self.addCleanup(self.delete_authentication_silo, name=name, force=True)
95 result, out, err = self.runcmd("domain", "auth", "silo", "create",
96 "--name", name,
97 "--user-authentication-policy", "User Policy")
98 self.assertIsNone(result, msg=err)
100 # Check silo that was created
101 silo = self.get_authentication_silo(name)
102 self.assertEqual(str(silo["cn"]), name)
103 self.assertIn("User Policy", str(silo["msDS-UserAuthNPolicy"]))
104 self.assertEqual(str(silo["msDS-AuthNPolicySiloEnforced"]), "TRUE")
106 def test_create__multiple_policies(self):
107 """Test creating a new authentication silo with multiple policies."""
108 name = self.unique_name()
110 self.addCleanup(self.delete_authentication_silo, name=name, force=True)
111 result, out, err = self.runcmd("domain", "auth", "silo", "create",
112 "--name", name,
113 "--user-authentication-policy",
114 "User Policy",
115 "--service-authentication-policy",
116 "Service Policy",
117 "--computer-authentication-policy",
118 "Computer Policy")
119 self.assertIsNone(result, msg=err)
121 # Check silo that was created.
122 silo = self.get_authentication_silo(name)
123 self.assertEqual(str(silo["cn"]), name)
124 self.assertIn("User Policy", str(silo["msDS-UserAuthNPolicy"]))
125 self.assertIn("Service Policy", str(silo["msDS-ServiceAuthNPolicy"]))
126 self.assertIn("Computer Policy", str(silo["msDS-ComputerAuthNPolicy"]))
127 self.assertEqual(str(silo["msDS-AuthNPolicySiloEnforced"]), "TRUE")
129 def test_create__policy_dn(self):
130 """Test creating a new authentication silo when policy is a dn."""
131 name = self.unique_name()
132 policy = self.get_authentication_policy("User Policy")
134 self.addCleanup(self.delete_authentication_silo, name=name, force=True)
135 result, out, err = self.runcmd("domain", "auth", "silo", "create",
136 "--name", name,
137 "--user-authentication-policy", policy["dn"])
138 self.assertIsNone(result, msg=err)
140 # Check silo that was created
141 silo = self.get_authentication_silo(name)
142 self.assertEqual(str(silo["cn"]), name)
143 self.assertIn(str(policy["name"]), str(silo["msDS-UserAuthNPolicy"]))
144 self.assertEqual(str(silo["msDS-AuthNPolicySiloEnforced"]), "TRUE")
146 def test_create__already_exists(self):
147 """Test creating a new authentication silo that already exists."""
148 result, out, err = self.runcmd("domain", "auth", "silo", "create",
149 "--name", "Developers",
150 "--user-authentication-policy", "User Policy")
151 self.assertEqual(result, -1)
152 self.assertIn("Authentication silo Developers already exists.", err)
154 def test_create__name_missing(self):
155 """Test create authentication silo without --name argument."""
156 result, out, err = self.runcmd("domain", "auth", "silo", "create",
157 "--user-authentication-policy", "User Policy")
158 self.assertEqual(result, -1)
159 self.assertIn("Argument --name is required.", err)
161 def test_create__audit(self):
162 """Test create authentication silo with --audit flag."""
163 name = self.unique_name()
165 self.addCleanup(self.delete_authentication_silo, name=name, force=True)
166 result, out, err = self.runcmd("domain", "auth", "silo", "create",
167 "--name", "auditPolicies",
168 "--user-authentication-policy", "User Policy",
169 "--name", name,
170 "--user-authentication-policy", "User Policy",
171 "--audit")
172 self.assertIsNone(result, msg=err)
174 # fetch and check silo
175 silo = self.get_authentication_silo(name)
176 self.assertEqual(str(silo["msDS-AuthNPolicySiloEnforced"]), "FALSE")
178 def test_create__enforce(self):
179 """Test create authentication silo with --enforce flag."""
180 name = self.unique_name()
182 self.addCleanup(self.delete_authentication_silo, name=name, force=True)
183 result, out, err = self.runcmd("domain", "auth", "silo", "create",
184 "--name", name,
185 "--user-authentication-policy", "User Policy",
186 "--enforce")
187 self.assertIsNone(result, msg=err)
189 # fetch and check silo
190 silo = self.get_authentication_silo(name)
191 self.assertEqual(str(silo["msDS-AuthNPolicySiloEnforced"]), "TRUE")
193 def test_create__audit_enforce_together(self):
194 """Test create authentication silo using both --audit and --enforce."""
195 name = self.unique_name()
197 result, out, err = self.runcmd("domain", "auth", "silo", "create",
198 "--name", name,
199 "--user-authentication-policy", "User Policy",
200 "--audit", "--enforce")
202 self.assertEqual(result, -1)
203 self.assertIn("--audit and --enforce cannot be used together.", err)
205 def test_create__protect_unprotect_together(self):
206 """Test create authentication silo using --protect and --unprotect."""
207 name = self.unique_name()
209 result, out, err = self.runcmd("domain", "auth", "silo", "create",
210 "--name", name,
211 "--user-authentication-policy", "User Policy",
212 "--protect", "--unprotect")
214 self.assertEqual(result, -1)
215 self.assertIn("--protect and --unprotect cannot be used together.", err)
217 def test_create__policy_notfound(self):
218 """Test create authentication silo with a policy that doesn't exist."""
219 name = self.unique_name()
221 result, out, err = self.runcmd("domain", "auth", "silo", "create",
222 "--name", name,
223 "--user-authentication-policy", "Invalid Policy")
225 self.assertEqual(result, -1)
226 self.assertIn("Authentication policy Invalid Policy not found.", err)
228 def test_create__fails(self):
229 """Test creating an authentication silo, but it fails."""
230 name = self.unique_name()
232 # Raise ModelError when ldb.add() is called.
233 with patch.object(SamDB, "add") as add_mock:
234 add_mock.side_effect = ModelError("Custom error message")
235 result, out, err = self.runcmd("domain", "auth", "silo", "create",
236 "--name", name,
237 "--user-authentication-policy", "User Policy")
238 self.assertEqual(result, -1)
239 self.assertIn("Custom error message", err)
241 def test_modify__description(self):
242 """Test modify authentication silo changing the description field."""
243 name = self.unique_name()
245 # Create a silo to modify for this test.
246 self.addCleanup(self.delete_authentication_silo, name=name, force=True)
247 self.runcmd("domain", "auth", "silo", "create", "--name", name)
249 result, out, err = self.runcmd("domain", "auth", "silo", "modify",
250 "--name", name,
251 "--description", "New Description")
252 self.assertIsNone(result, msg=err)
254 # check new value
255 silo = self.get_authentication_silo(name)
256 self.assertEqual(str(silo["description"]), "New Description")
258 def test_modify__audit_enforce(self):
259 """Test modify authentication silo setting --audit and --enforce."""
260 name = self.unique_name()
262 # Create a silo to modify for this test.
263 self.addCleanup(self.delete_authentication_silo, name=name, force=True)
264 self.runcmd("domain", "auth", "silo", "create", "--name", name)
266 result, out, err = self.runcmd("domain", "auth", "silo", "modify",
267 "--name", name,
268 "--audit")
269 self.assertIsNone(result, msg=err)
271 # Check silo is set to audit.
272 silo = self.get_authentication_silo(name)
273 self.assertEqual(str(silo["msDS-AuthNPolicySiloEnforced"]), "FALSE")
275 result, out, err = self.runcmd("domain", "auth", "silo", "modify",
276 "--name", name,
277 "--enforce")
278 self.assertIsNone(result, msg=err)
280 # Check is set to enforce.
281 silo = self.get_authentication_silo(name)
282 self.assertEqual(str(silo["msDS-AuthNPolicySiloEnforced"]), "TRUE")
284 def test_modify__protect_unprotect(self):
285 """Test modify un-protecting and protecting an authentication silo."""
286 name = self.unique_name()
288 # Create a silo to modify for this test.
289 self.addCleanup(self.delete_authentication_silo, name=name, force=True)
290 self.runcmd("domain", "auth", "silo", "create", "--name", name)
292 utils = SDUtils(self.samdb)
293 result, out, err = self.runcmd("domain", "auth", "silo", "modify",
294 "--name", name,
295 "--protect")
296 self.assertIsNone(result, msg=err)
298 # Check that silo was protected.
299 silo = self.get_authentication_silo(name)
300 desc = utils.get_sd_as_sddl(silo["dn"])
301 self.assertIn("(D;;DTSD;;;WD)", desc)
303 result, out, err = self.runcmd("domain", "auth", "silo", "modify",
304 "--name", name,
305 "--unprotect")
306 self.assertIsNone(result, msg=err)
308 # Check that silo was unprotected.
309 silo = self.get_authentication_silo(name)
310 desc = utils.get_sd_as_sddl(silo["dn"])
311 self.assertNotIn("(D;;DTSD;;;WD)", desc)
313 def test_modify__audit_enforce_together(self):
314 """Test modify silo doesn't allow both --audit and --enforce."""
315 result, out, err = self.runcmd("domain", "auth", "silo", "modify",
316 "--name", "QA",
317 "--audit", "--enforce")
319 self.assertEqual(result, -1)
320 self.assertIn("--audit and --enforce cannot be used together.", err)
322 def test_modify__protect_unprotect_together(self):
323 """Test modify silo using both --protect and --unprotect."""
324 result, out, err = self.runcmd("domain", "auth", "silo", "modify",
325 "--name", "Developers",
326 "--protect", "--unprotect")
327 self.assertEqual(result, -1)
328 self.assertIn("--protect and --unprotect cannot be used together.", err)
330 def test_modify__notfound(self):
331 """Test modify an authentication silo that doesn't exist."""
332 result, out, err = self.runcmd("domain", "auth", "silo", "modify",
333 "--name", "doesNotExist",
334 "--description=NewDescription")
335 self.assertEqual(result, -1)
336 self.assertIn("Authentication silo doesNotExist not found.", err)
338 def test_modify__name_missing(self):
339 """Test modify authentication silo without --name argument."""
340 result, out, err = self.runcmd("domain", "auth", "silo", "modify")
341 self.assertEqual(result, -1)
342 self.assertIn("Argument --name is required.", err)
344 def test_modify__fails(self):
345 """Test modify authentication silo, but it fails."""
346 # Raise ModelError when ldb.modify() is called.
347 with patch.object(SamDB, "modify") as add_mock:
348 add_mock.side_effect = ModelError("Custom error message")
349 result, out, err = self.runcmd("domain", "auth", "silo", "modify",
350 "--name", "Developers",
351 "--description", "Devs")
352 self.assertEqual(result, -1)
353 self.assertIn("Custom error message", err)
355 def test_authentication_silo_delete(self):
356 """Test deleting an authentication silo that is not protected."""
357 name = self.unique_name()
359 # Create non-protected authentication silo.
360 result, out, err = self.runcmd("domain", "auth", "silo", "create",
361 "--name", name,
362 "--user-authentication-policy", "User Policy")
363 self.assertIsNone(result, msg=err)
364 silo = self.get_authentication_silo(name)
365 self.assertIsNotNone(silo)
367 # Do the deletion.
368 result, out, err = self.runcmd("domain", "auth", "silo", "delete",
369 "--name", name)
370 self.assertIsNone(result, msg=err)
372 # Authentication silo shouldn't exist anymore.
373 silo = self.get_authentication_silo(name)
374 self.assertIsNone(silo)
376 def test_delete__protected(self):
377 """Test deleting a protected auth silo, with and without --force."""
378 name = self.unique_name()
380 # Create protected authentication silo.
381 result, out, err = self.runcmd("domain", "auth", "silo", "create",
382 "--name", name,
383 "--user-authentication-policy", "User Policy",
384 "--protect")
385 self.assertIsNone(result, msg=err)
386 silo = self.get_authentication_silo(name)
387 self.assertIsNotNone(silo)
389 # Do the deletion.
390 result, out, err = self.runcmd("domain", "auth", "silo", "delete",
391 "--name", name)
392 self.assertEqual(result, -1)
394 # Authentication silo should still exist.
395 silo = self.get_authentication_silo(name)
396 self.assertIsNotNone(silo)
398 # Try a force delete instead.
399 result, out, err = self.runcmd("domain", "auth", "silo", "delete",
400 "--name", name, "--force")
401 self.assertIsNone(result, msg=err)
403 # Authentication silo shouldn't exist anymore.
404 silo = self.get_authentication_silo(name)
405 self.assertIsNone(silo)
407 def test_delete__notfound(self):
408 """Test deleting an authentication silo that doesn't exist."""
409 result, out, err = self.runcmd("domain", "auth", "silo", "delete",
410 "--name", "doesNotExist")
411 self.assertEqual(result, -1)
412 self.assertIn("Authentication silo doesNotExist not found.", err)
414 def test_delete__name_required(self):
415 """Test deleting an authentication silo without --name argument."""
416 result, out, err = self.runcmd("domain", "auth", "silo", "delete")
417 self.assertEqual(result, -1)
418 self.assertIn("Argument --name is required.", err)
420 def test_delete__force_fails(self):
421 """Test deleting an authentication silo with --force, but it fails."""
422 name = self.unique_name()
424 # Create protected authentication silo.
425 self.addCleanup(self.delete_authentication_silo, name=name, force=True)
426 result, out, err = self.runcmd("domain", "auth", "silo", "create",
427 "--name", name,
428 "--user-authentication-policy", "User Policy",
429 "--protect")
430 self.assertIsNone(result, msg=err)
432 # Silo exists
433 silo = self.get_authentication_silo(name)
434 self.assertIsNotNone(silo)
436 # Try doing delete with --force.
437 # Patch SDUtils.dacl_delete_aces with a Mock that raises ModelError.
438 with patch.object(SDUtils, "dacl_delete_aces") as delete_mock:
439 delete_mock.side_effect = ModelError("Custom error message")
440 result, out, err = self.runcmd("domain", "auth", "silo", "delete",
441 "--name", name,
442 "--force")
443 self.assertEqual(result, -1)
444 self.assertIn("Custom error message", err)
446 def test_delete__fails(self):
447 """Test deleting an authentication silo, but it fails."""
448 name = self.unique_name()
450 # Create regular authentication silo.
451 self.addCleanup(self.delete_authentication_silo, name=name, force=True)
452 result, out, err = self.runcmd("domain", "auth", "silo", "create",
453 "--name", name,
454 "--user-authentication-policy", "User Policy")
455 self.assertIsNone(result, msg=err)
457 # Silo exists
458 silo = self.get_authentication_silo(name)
459 self.assertIsNotNone(silo)
461 # Raise ModelError when ldb.delete() is called.
462 with patch.object(SamDB, "delete") as delete_mock:
463 delete_mock.side_effect = ModelError("Custom error message")
464 result, out, err = self.runcmd("domain", "auth", "silo", "delete",
465 "--name", name)
466 self.assertEqual(result, -1)
467 self.assertIn("Custom error message", err)
469 # When not using --force we get a hint.
470 self.assertIn("Try --force", err)
472 def test_delete__protected_fails(self):
473 """Test deleting an authentication silo, but it fails."""
474 name = self.unique_name()
476 # Create protected authentication silo.
477 self.addCleanup(self.delete_authentication_silo, name=name, force=True)
478 result, out, err = self.runcmd("domain", "auth", "silo", "create",
479 "--name", name,
480 "--user-authentication-policy", "User Policy",
481 "--protect")
482 self.assertIsNone(result, msg=err)
484 # Silo exists
485 silo = self.get_authentication_silo(name)
486 self.assertIsNotNone(silo)
488 # Raise ModelError when ldb.delete() is called.
489 with patch.object(SamDB, "delete") as delete_mock:
490 delete_mock.side_effect = ModelError("Custom error message")
491 result, out, err = self.runcmd("domain", "auth", "silo", "delete",
492 "--name", name,
493 "--force")
494 self.assertEqual(result, -1)
495 self.assertIn("Custom error message", err)
497 # When using --force we don't get the hint.
498 self.assertNotIn("Try --force", err)
501 class AuthSiloMemberCmdTestCase(SiloTest):
503 def setUp(self):
504 super().setUp()
506 # Create an organisational unit to test in.
507 self.ou = self.samdb.get_default_basedn()
508 self.ou.add_child("OU=Domain Auth Tests")
509 self.samdb.create_ou(self.ou)
510 self.addCleanup(self.samdb.delete, self.ou, ["tree_delete:1"])
512 # Grant member access to silos
513 self.grant_silo_access("Developers", "bob")
514 self.grant_silo_access("Developers", "jane")
515 self.grant_silo_access("Managers", "alice")
517 def create_computer(self, name):
518 """Create a Computer and return the dn."""
519 dn = f"CN={name},{self.ou}"
520 self.samdb.newcomputer(name, self.ou)
521 return dn
523 def grant_silo_access(self, silo, member):
524 """Grant a member access to an authentication silo."""
525 result, out, err = self.runcmd("domain", "auth", "silo",
526 "member", "grant",
527 "--name", silo, "--member", member)
529 self.assertIsNone(result, msg=err)
530 self.assertIn(
531 f"User {member} granted access to the authentication silo {silo}",
532 out)
533 self.addCleanup(self.revoke_silo_access, silo, member)
535 def revoke_silo_access(self, silo, member):
536 """Revoke a member from an authentication silo."""
537 result, out, err = self.runcmd("domain", "auth", "silo",
538 "member", "revoke",
539 "--name", silo, "--member", member)
541 self.assertIsNone(result, msg=err)
543 def test_member_list(self):
544 """Test listing authentication policy members in list format."""
545 alice = self.get_user("alice")
546 jane = self.get_user("jane")
547 bob = self.get_user("bob")
549 result, out, err = self.runcmd("domain", "auth", "silo",
550 "member", "list",
551 "--name", "Developers")
553 self.assertIsNone(result, msg=err)
554 self.assertIn(str(bob.dn), out)
555 self.assertIn(str(jane.dn), out)
556 self.assertNotIn(str(alice.dn), out)
558 def test_member_list___json(self):
559 """Test listing authentication policy members list in json format."""
560 alice = self.get_user("alice")
561 jane = self.get_user("jane")
562 bob = self.get_user("bob")
564 result, out, err = self.runcmd("domain", "auth", "silo",
565 "member", "list",
566 "--name", "Developers", "--json")
568 self.assertIsNone(result, msg=err)
569 members = json.loads(out)
570 members_dn = [member["dn"] for member in members]
571 self.assertIn(str(bob.dn), members_dn)
572 self.assertIn(str(jane.dn), members_dn)
573 self.assertNotIn(str(alice.dn), members_dn)
575 def test_member_list__name_missing(self):
576 """Test list authentication policy members without the name argument."""
577 result, out, err = self.runcmd("domain", "auth", "silo",
578 "member", "list")
580 self.assertIsNotNone(result)
581 self.assertIn("Argument --name is required.", err)
583 def test_member_grant__user(self):
584 """Test adding a user to an authentication silo."""
585 self.grant_silo_access("Developers", "joe")
587 # Check if member is in silo
588 user = self.get_user("joe")
589 silo = self.get_authentication_silo("Developers")
590 members = [str(member) for member in silo["msDS-AuthNPolicySiloMembers"]]
591 self.assertIn(str(user.dn), members)
593 def test_member_grant__computer(self):
594 """Test adding a computer to an authentication silo"""
595 name = self.unique_name()
596 computer = self.create_computer(name)
597 silo = "Developers"
599 # Don't use self.grant_silo_member as it will try to clean up the user.
600 result, out, err = self.runcmd("domain", "auth", "silo",
601 "member", "grant",
602 "--name", silo,
603 "--member", computer)
605 self.assertIsNone(result, msg=err)
606 self.assertIn(
607 f"User {name}$ granted access to the authentication silo {silo} (unassigned).",
608 out)
610 def test_member_grant__unknown_user(self):
611 """Test adding an unknown user to an authentication silo."""
612 result, out, err = self.runcmd("domain", "auth", "silo",
613 "member", "grant",
614 "--name", "Developers",
615 "--member", "does_not_exist")
617 self.assertIsNotNone(result)
618 self.assertIn("User does_not_exist not found.", err)