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/>.
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
):
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",
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
:
58 self
.assertIn("msDS-AuthNPolicySilo", list(silo
["objectClass"]))
59 self
.assertIn("description", silo
)
60 self
.assertIn("msDS-UserAuthNPolicy", silo
)
61 self
.assertIn("objectGUID", silo
)
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",
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",
113 "--user-authentication-policy",
115 "--service-authentication-policy",
117 "--computer-authentication-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",
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",
170 "--user-authentication-policy", "User Policy",
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",
185 "--user-authentication-policy", "User Policy",
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",
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",
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",
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",
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",
251 "--description", "New Description")
252 self
.assertIsNone(result
, msg
=err
)
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",
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",
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",
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",
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",
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",
362 "--user-authentication-policy", "User Policy")
363 self
.assertIsNone(result
, msg
=err
)
364 silo
= self
.get_authentication_silo(name
)
365 self
.assertIsNotNone(silo
)
368 result
, out
, err
= self
.runcmd("domain", "auth", "silo", "delete",
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",
383 "--user-authentication-policy", "User Policy",
385 self
.assertIsNone(result
, msg
=err
)
386 silo
= self
.get_authentication_silo(name
)
387 self
.assertIsNotNone(silo
)
390 result
, out
, err
= self
.runcmd("domain", "auth", "silo", "delete",
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",
428 "--user-authentication-policy", "User Policy",
430 self
.assertIsNone(result
, msg
=err
)
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",
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",
454 "--user-authentication-policy", "User Policy")
455 self
.assertIsNone(result
, msg
=err
)
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",
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",
480 "--user-authentication-policy", "User Policy",
482 self
.assertIsNone(result
, msg
=err
)
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",
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
):
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
)
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",
527 "--name", silo
, "--member", member
)
529 self
.assertIsNone(result
, msg
=err
)
531 f
"User {member} granted access to the authentication silo {silo}",
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",
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",
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",
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",
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
)
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",
603 "--member", computer
)
605 self
.assertIsNone(result
, msg
=err
)
607 f
"User {name}$ granted access to the authentication silo {silo} (unassigned).",
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",
614 "--name", "Developers",
615 "--member", "does_not_exist")
617 self
.assertIsNotNone(result
)
618 self
.assertIn("User does_not_exist not found.", err
)