1"""
2    :codeauthor: Mike Place <mp@saltstack.com>
3"""
4
5
6import time
7
8# Import Salt libraries
9import salt.master
10import salt.utils.platform
11from salt import auth
12from salt.exceptions import SaltDeserializationError
13from tests.support.case import ModuleCase
14from tests.support.mock import MagicMock, call, patch
15from tests.support.unit import TestCase, skipIf
16
17
18class LoadAuthTestCase(TestCase):
19    def setUp(self):  # pylint: disable=W0221
20        patches = (
21            ("salt.payload.Serial", None),
22            (
23                "salt.loader.auth",
24                dict(
25                    return_value={
26                        "pam.auth": "fake_func_str",
27                        "pam.groups": "fake_groups_function_str",
28                    }
29                ),
30            ),
31            (
32                "salt.loader.eauth_tokens",
33                dict(
34                    return_value={
35                        "localfs.mk_token": "fake_func_mktok",
36                        "localfs.get_token": "fake_func_gettok",
37                        "localfs.rm_roken": "fake_func_rmtok",
38                    }
39                ),
40            ),
41        )
42        for mod, mock in patches:
43            if mock:
44                patcher = patch(mod, **mock)
45            else:
46                patcher = patch(mod)
47            patcher.start()
48            self.addCleanup(patcher.stop)
49        self.lauth = auth.LoadAuth({})  # Load with empty opts
50
51    def test_get_tok_with_broken_file_will_remove_bad_token(self):
52        fake_get_token = MagicMock(side_effect=SaltDeserializationError("hi"))
53        patch_opts = patch.dict(self.lauth.opts, {"eauth_tokens": "testfs"})
54        patch_get_token = patch.dict(
55            self.lauth.tokens,
56            {"testfs.get_token": fake_get_token},
57        )
58        mock_rm_token = MagicMock()
59        patch_rm_token = patch.object(self.lauth, "rm_token", mock_rm_token)
60        with patch_opts, patch_get_token, patch_rm_token:
61            expected_token = "fnord"
62            self.lauth.get_tok(expected_token)
63            mock_rm_token.assert_called_with(expected_token)
64
65    def test_get_tok_with_no_expiration_should_remove_bad_token(self):
66        fake_get_token = MagicMock(return_value={"no_expire_here": "Nope"})
67        patch_opts = patch.dict(self.lauth.opts, {"eauth_tokens": "testfs"})
68        patch_get_token = patch.dict(
69            self.lauth.tokens,
70            {"testfs.get_token": fake_get_token},
71        )
72        mock_rm_token = MagicMock()
73        patch_rm_token = patch.object(self.lauth, "rm_token", mock_rm_token)
74        with patch_opts, patch_get_token, patch_rm_token:
75            expected_token = "fnord"
76            self.lauth.get_tok(expected_token)
77            mock_rm_token.assert_called_with(expected_token)
78
79    def test_get_tok_with_expire_before_current_time_should_remove_token(self):
80        fake_get_token = MagicMock(return_value={"expire": time.time() - 1})
81        patch_opts = patch.dict(self.lauth.opts, {"eauth_tokens": "testfs"})
82        patch_get_token = patch.dict(
83            self.lauth.tokens,
84            {"testfs.get_token": fake_get_token},
85        )
86        mock_rm_token = MagicMock()
87        patch_rm_token = patch.object(self.lauth, "rm_token", mock_rm_token)
88        with patch_opts, patch_get_token, patch_rm_token:
89            expected_token = "fnord"
90            self.lauth.get_tok(expected_token)
91            mock_rm_token.assert_called_with(expected_token)
92
93    def test_get_tok_with_valid_expiration_should_return_token(self):
94        expected_token = {"expire": time.time() + 1}
95        fake_get_token = MagicMock(return_value=expected_token)
96        patch_opts = patch.dict(self.lauth.opts, {"eauth_tokens": "testfs"})
97        patch_get_token = patch.dict(
98            self.lauth.tokens,
99            {"testfs.get_token": fake_get_token},
100        )
101        mock_rm_token = MagicMock()
102        patch_rm_token = patch.object(self.lauth, "rm_token", mock_rm_token)
103        with patch_opts, patch_get_token, patch_rm_token:
104            token_name = "fnord"
105            actual_token = self.lauth.get_tok(token_name)
106            mock_rm_token.assert_not_called()
107            assert expected_token is actual_token, "Token was not returned"
108
109    def test_load_name(self):
110        valid_eauth_load = {
111            "username": "test_user",
112            "show_timeout": False,
113            "test_password": "",
114            "eauth": "pam",
115        }
116
117        # Test a case where the loader auth doesn't have the auth type
118        without_auth_type = dict(valid_eauth_load)
119        without_auth_type.pop("eauth")
120        ret = self.lauth.load_name(without_auth_type)
121        self.assertEqual(
122            ret, "", "Did not bail when the auth loader didn't have the auth type."
123        )
124
125        # Test a case with valid params
126        with patch(
127            "salt.utils.args.arg_lookup",
128            MagicMock(return_value={"args": ["username", "password"]}),
129        ) as format_call_mock:
130            expected_ret = call("fake_func_str")
131            ret = self.lauth.load_name(valid_eauth_load)
132            format_call_mock.assert_has_calls((expected_ret,), any_order=True)
133            self.assertEqual(ret, "test_user")
134
135    def test_get_groups(self):
136        valid_eauth_load = {
137            "username": "test_user",
138            "show_timeout": False,
139            "test_password": "",
140            "eauth": "pam",
141        }
142        with patch("salt.utils.args.format_call") as format_call_mock:
143            expected_ret = call(
144                "fake_groups_function_str",
145                {
146                    "username": "test_user",
147                    "test_password": "",
148                    "show_timeout": False,
149                    "eauth": "pam",
150                },
151                expected_extra_kws=auth.AUTH_INTERNAL_KEYWORDS,
152            )
153            self.lauth.get_groups(valid_eauth_load)
154            format_call_mock.assert_has_calls((expected_ret,), any_order=True)
155
156
157class MasterACLTestCase(ModuleCase):
158    """
159    A class to check various aspects of the publisher ACL system
160    """
161
162    def setUp(self):
163        self.fire_event_mock = MagicMock(return_value="dummy_tag")
164        self.addCleanup(delattr, self, "fire_event_mock")
165        opts = self.get_temp_config("master")
166
167        patches = (
168            ("zmq.Context", MagicMock()),
169            ("salt.payload.Serial.dumps", MagicMock()),
170            ("salt.master.tagify", MagicMock()),
171            ("salt.utils.event.SaltEvent.fire_event", self.fire_event_mock),
172            ("salt.auth.LoadAuth.time_auth", MagicMock(return_value=True)),
173            ("salt.minion.MasterMinion", MagicMock()),
174            ("salt.utils.verify.check_path_traversal", MagicMock()),
175            ("salt.client.get_local_client", MagicMock()),
176        )
177        for mod, mock in patches:
178            patcher = patch(mod, mock)
179            patcher.start()
180            self.addCleanup(patcher.stop)
181
182        opts["publisher_acl"] = {}
183        opts["publisher_acl_blacklist"] = {}
184        opts["master_job_cache"] = ""
185        opts["sign_pub_messages"] = False
186        opts["con_cache"] = ""
187        opts["external_auth"] = {}
188        opts["external_auth"]["pam"] = {
189            "test_user": [
190                {"*": ["test.ping"]},
191                {"minion_glob*": ["foo.bar"]},
192                {"minion_func_test": ["func_test.*"]},
193            ],
194            "test_group%": [{"*": ["test.echo"]}],
195            "test_user_mminion": [{"target_minion": ["test.ping"]}],
196            "*": [{"my_minion": ["my_mod.my_func"]}],
197            "test_user_func": [
198                {
199                    "*": [
200                        {"test.echo": {"args": ["MSG:.*"]}},
201                        {
202                            "test.echo": {
203                                "kwargs": {
204                                    "text": "KWMSG:.*",
205                                    "anything": ".*",
206                                    "none": None,
207                                }
208                            }
209                        },
210                        {
211                            "my_mod.*": {
212                                "args": ["a.*", "b.*"],
213                                "kwargs": {"kwa": "kwa.*", "kwb": "kwb"},
214                            }
215                        },
216                    ]
217                },
218                {
219                    "minion1": [
220                        {"test.echo": {"args": ["TEST", None, "TEST.*"]}},
221                        {"test.empty": {}},
222                    ]
223                },
224            ],
225        }
226        self.clear = salt.master.ClearFuncs(opts, MagicMock())
227        self.addCleanup(self.clear.destroy)
228        self.addCleanup(delattr, self, "clear")
229
230        # overwrite the _send_pub method so we don't have to serialize MagicMock
231        self.clear._send_pub = lambda payload: True
232
233        # make sure to return a JID, instead of a mock
234        self.clear.mminion.returners = {".prep_jid": lambda x: 1}
235
236        self.valid_clear_load = {
237            "tgt_type": "glob",
238            "jid": "",
239            "cmd": "publish",
240            "tgt": "test_minion",
241            "kwargs": {
242                "username": "test_user",
243                "password": "test_password",
244                "show_timeout": False,
245                "eauth": "pam",
246                "show_jid": False,
247            },
248            "ret": "",
249            "user": "test_user",
250            "key": "",
251            "arg": "",
252            "fun": "test.ping",
253        }
254        self.addCleanup(delattr, self, "valid_clear_load")
255
256    @skipIf(salt.utils.platform.is_windows(), "PAM eauth not available on Windows")
257    def test_master_publish_name(self):
258        """
259        Test to ensure a simple name can auth against a given function.
260        This tests to ensure test_user can access test.ping but *not* sys.doc
261        """
262        _check_minions_return = {"minions": ["some_minions"], "missing": []}
263        with patch(
264            "salt.utils.minions.CkMinions.check_minions",
265            MagicMock(return_value=_check_minions_return),
266        ):
267            # Can we access test.ping?
268            self.clear.publish(self.valid_clear_load)
269            self.assertEqual(self.fire_event_mock.call_args[0][0]["fun"], "test.ping")
270
271            # Are we denied access to sys.doc?
272            sys_doc_load = self.valid_clear_load
273            sys_doc_load["fun"] = "sys.doc"
274            self.clear.publish(sys_doc_load)
275            self.assertNotEqual(
276                self.fire_event_mock.call_args[0][0]["fun"], "sys.doc"
277            )  # If sys.doc were to fire, this would match
278
279    def test_master_publish_group(self):
280        """
281        Tests to ensure test_group can access test.echo but *not* sys.doc
282        """
283        _check_minions_return = {"minions": ["some_minions"], "missing": []}
284        with patch(
285            "salt.utils.minions.CkMinions.check_minions",
286            MagicMock(return_value=_check_minions_return),
287        ):
288            self.valid_clear_load["kwargs"]["user"] = "new_user"
289            self.valid_clear_load["fun"] = "test.echo"
290            self.valid_clear_load["arg"] = "hello"
291            with patch(
292                "salt.auth.LoadAuth.get_groups",
293                return_value=["test_group", "second_test_group"],
294            ):
295                self.clear.publish(self.valid_clear_load)
296            # Did we fire test.echo?
297            self.assertEqual(self.fire_event_mock.call_args[0][0]["fun"], "test.echo")
298
299            # Request sys.doc
300            self.valid_clear_load["fun"] = "sys.doc"
301            # Did we fire it?
302            self.assertNotEqual(self.fire_event_mock.call_args[0][0]["fun"], "sys.doc")
303
304    def test_master_publish_some_minions(self):
305        """
306        Tests to ensure we can only target minions for which we
307        have permission with publisher acl.
308
309        Note that in order for these sorts of tests to run correctly that
310        you should NOT patch check_minions!
311        """
312        self.valid_clear_load["kwargs"]["username"] = "test_user_mminion"
313        self.valid_clear_load["user"] = "test_user_mminion"
314        self.clear.publish(self.valid_clear_load)
315        self.assertEqual(self.fire_event_mock.mock_calls, [])
316
317    def test_master_not_user_glob_all(self):
318        """
319        Test to ensure that we DO NOT access to a given
320        function to all users with publisher acl. ex:
321
322        '*':
323            my_minion:
324                - my_func
325
326        Yes, this seems like a bit of a no-op test but it's
327        here to document that this functionality
328        is NOT supported currently.
329
330        WARNING: Do not patch this wit
331        """
332        self.valid_clear_load["kwargs"]["username"] = "NOT_A_VALID_USERNAME"
333        self.valid_clear_load["user"] = "NOT_A_VALID_USERNAME"
334        self.valid_clear_load["fun"] = "test.ping"
335        self.clear.publish(self.valid_clear_load)
336        self.assertEqual(self.fire_event_mock.mock_calls, [])
337
338    @skipIf(salt.utils.platform.is_windows(), "PAM eauth not available on Windows")
339    def test_master_minion_glob(self):
340        """
341        Test to ensure we can allow access to a given
342        function for a user to a subset of minions
343        selected by a glob. ex:
344
345        test_user:
346            'minion_glob*':
347              - glob_mod.glob_func
348
349        This test is a bit tricky, because ultimately the real functionality
350        lies in what's returned from check_minions, but this checks a limited
351        amount of logic on the way there as well. Note the inline patch.
352        """
353        requested_function = "foo.bar"
354        requested_tgt = "minion_glob1"
355        self.valid_clear_load["tgt"] = requested_tgt
356        self.valid_clear_load["fun"] = requested_function
357        _check_minions_return = {"minions": ["minion_glob1"], "missing": []}
358        with patch(
359            "salt.utils.minions.CkMinions.check_minions",
360            MagicMock(return_value=_check_minions_return),
361        ):  # Assume that there is a listening minion match
362            self.clear.publish(self.valid_clear_load)
363        self.assertTrue(
364            self.fire_event_mock.called,
365            "Did not fire {} for minion tgt {}".format(
366                requested_function, requested_tgt
367            ),
368        )
369        self.assertEqual(
370            self.fire_event_mock.call_args[0][0]["fun"],
371            requested_function,
372            "Did not fire {} for minion glob".format(requested_function),
373        )
374
375    def test_master_function_glob(self):
376        """
377        Test to ensure that we can allow access to a given
378        set of functions in an execution module as selected
379        by a glob. ex:
380
381        my_user:
382            my_minion:
383                'test.*'
384        """
385        # Unimplemented
386
387    @skipIf(salt.utils.platform.is_windows(), "PAM eauth not available on Windows")
388    def test_args_empty_spec(self):
389        """
390        Test simple arg restriction allowed.
391
392        'test_user_func':
393            minion1:
394                - test.empty:
395        """
396        _check_minions_return = {"minions": ["minion1"], "missing": []}
397        with patch(
398            "salt.utils.minions.CkMinions.check_minions",
399            MagicMock(return_value=_check_minions_return),
400        ):
401            self.valid_clear_load["kwargs"].update({"username": "test_user_func"})
402            self.valid_clear_load.update(
403                {
404                    "user": "test_user_func",
405                    "tgt": "minion1",
406                    "fun": "test.empty",
407                    "arg": ["TEST"],
408                }
409            )
410            self.clear.publish(self.valid_clear_load)
411            self.assertEqual(self.fire_event_mock.call_args[0][0]["fun"], "test.empty")
412
413    @skipIf(salt.utils.platform.is_windows(), "PAM eauth not available on Windows")
414    def test_args_simple_match(self):
415        """
416        Test simple arg restriction allowed.
417
418        'test_user_func':
419            minion1:
420                - test.echo:
421                    args:
422                        - 'TEST'
423                        - 'TEST.*'
424        """
425        _check_minions_return = {"minions": ["minion1"], "missing": []}
426        with patch(
427            "salt.utils.minions.CkMinions.check_minions",
428            MagicMock(return_value=_check_minions_return),
429        ):
430            self.valid_clear_load["kwargs"].update({"username": "test_user_func"})
431            self.valid_clear_load.update(
432                {
433                    "user": "test_user_func",
434                    "tgt": "minion1",
435                    "fun": "test.echo",
436                    "arg": ["TEST", "any", "TEST ABC"],
437                }
438            )
439            self.clear.publish(self.valid_clear_load)
440            self.assertEqual(self.fire_event_mock.call_args[0][0]["fun"], "test.echo")
441
442    @skipIf(salt.utils.platform.is_windows(), "PAM eauth not available on Windows")
443    def test_args_more_args(self):
444        """
445        Test simple arg restriction allowed to pass unlisted args.
446
447        'test_user_func':
448            minion1:
449                - test.echo:
450                    args:
451                        - 'TEST'
452                        - 'TEST.*'
453        """
454        _check_minions_return = {"minions": ["minion1"], "missing": []}
455        with patch(
456            "salt.utils.minions.CkMinions.check_minions",
457            MagicMock(return_value=_check_minions_return),
458        ):
459            self.valid_clear_load["kwargs"].update({"username": "test_user_func"})
460            self.valid_clear_load.update(
461                {
462                    "user": "test_user_func",
463                    "tgt": "minion1",
464                    "fun": "test.echo",
465                    "arg": [
466                        "TEST",
467                        "any",
468                        "TEST ABC",
469                        "arg 3",
470                        {"kwarg1": "val1", "__kwarg__": True},
471                    ],
472                }
473            )
474            self.clear.publish(self.valid_clear_load)
475            self.assertEqual(self.fire_event_mock.call_args[0][0]["fun"], "test.echo")
476
477    def test_args_simple_forbidden(self):
478        """
479        Test simple arg restriction forbidden.
480
481        'test_user_func':
482            minion1:
483                - test.echo:
484                    args:
485                        - 'TEST'
486                        - 'TEST.*'
487        """
488        _check_minions_return = {"minions": ["minion1"], "missing": []}
489        with patch(
490            "salt.utils.minions.CkMinions.check_minions",
491            MagicMock(return_value=_check_minions_return),
492        ):
493            self.valid_clear_load["kwargs"].update({"username": "test_user_func"})
494            # Wrong last arg
495            self.valid_clear_load.update(
496                {
497                    "user": "test_user_func",
498                    "tgt": "minion1",
499                    "fun": "test.echo",
500                    "arg": ["TEST", "any", "TESLA"],
501                }
502            )
503            self.clear.publish(self.valid_clear_load)
504            self.assertEqual(self.fire_event_mock.mock_calls, [])
505            # Wrong first arg
506            self.valid_clear_load["arg"] = ["TES", "any", "TEST1234"]
507            self.clear.publish(self.valid_clear_load)
508            self.assertEqual(self.fire_event_mock.mock_calls, [])
509            # Missing the last arg
510            self.valid_clear_load["arg"] = ["TEST", "any"]
511            self.clear.publish(self.valid_clear_load)
512            self.assertEqual(self.fire_event_mock.mock_calls, [])
513            # No args
514            self.valid_clear_load["arg"] = []
515            self.clear.publish(self.valid_clear_load)
516            self.assertEqual(self.fire_event_mock.mock_calls, [])
517
518    @skipIf(salt.utils.platform.is_windows(), "PAM eauth not available on Windows")
519    def test_args_kwargs_match(self):
520        """
521        Test simple kwargs restriction allowed.
522
523        'test_user_func':
524            '*':
525                - test.echo:
526                    kwargs:
527                        text: 'KWMSG:.*'
528        """
529        _check_minions_return = {"minions": ["some_minions"], "missing": []}
530        with patch(
531            "salt.utils.minions.CkMinions.check_minions",
532            MagicMock(return_value=_check_minions_return),
533        ):
534            self.valid_clear_load["kwargs"].update({"username": "test_user_func"})
535            self.valid_clear_load.update(
536                {
537                    "user": "test_user_func",
538                    "tgt": "*",
539                    "fun": "test.echo",
540                    "arg": [
541                        {
542                            "text": "KWMSG: a message",
543                            "anything": "hello all",
544                            "none": "hello none",
545                            "__kwarg__": True,
546                        }
547                    ],
548                }
549            )
550            self.clear.publish(self.valid_clear_load)
551            self.assertEqual(self.fire_event_mock.call_args[0][0]["fun"], "test.echo")
552
553    def test_args_kwargs_mismatch(self):
554        """
555        Test simple kwargs restriction allowed.
556
557        'test_user_func':
558            '*':
559                - test.echo:
560                    kwargs:
561                        text: 'KWMSG:.*'
562        """
563        _check_minions_return = {"minions": ["some_minions"], "missing": []}
564        with patch(
565            "salt.utils.minions.CkMinions.check_minions",
566            MagicMock(return_value=_check_minions_return),
567        ):
568            self.valid_clear_load["kwargs"].update({"username": "test_user_func"})
569            self.valid_clear_load.update(
570                {"user": "test_user_func", "tgt": "*", "fun": "test.echo"}
571            )
572            # Wrong kwarg value
573            self.valid_clear_load["arg"] = [
574                {
575                    "text": "KWMSG a message",
576                    "anything": "hello all",
577                    "none": "hello none",
578                    "__kwarg__": True,
579                }
580            ]
581            self.clear.publish(self.valid_clear_load)
582            self.assertEqual(self.fire_event_mock.mock_calls, [])
583            # Missing kwarg value
584            self.valid_clear_load["arg"] = [
585                {"anything": "hello all", "none": "hello none", "__kwarg__": True}
586            ]
587            self.clear.publish(self.valid_clear_load)
588            self.assertEqual(self.fire_event_mock.mock_calls, [])
589            self.valid_clear_load["arg"] = [{"__kwarg__": True}]
590            self.clear.publish(self.valid_clear_load)
591            self.assertEqual(self.fire_event_mock.mock_calls, [])
592            self.valid_clear_load["arg"] = [{}]
593            self.clear.publish(self.valid_clear_load)
594            self.assertEqual(self.fire_event_mock.mock_calls, [])
595            self.valid_clear_load["arg"] = []
596            self.clear.publish(self.valid_clear_load)
597            self.assertEqual(self.fire_event_mock.mock_calls, [])
598            # Missing kwarg allowing any value
599            self.valid_clear_load["arg"] = [
600                {"text": "KWMSG: a message", "none": "hello none", "__kwarg__": True}
601            ]
602            self.clear.publish(self.valid_clear_load)
603            self.assertEqual(self.fire_event_mock.mock_calls, [])
604            self.valid_clear_load["arg"] = [
605                {"text": "KWMSG: a message", "anything": "hello all", "__kwarg__": True}
606            ]
607            self.clear.publish(self.valid_clear_load)
608            self.assertEqual(self.fire_event_mock.mock_calls, [])
609
610    @skipIf(salt.utils.platform.is_windows(), "PAM eauth not available on Windows")
611    def test_args_mixed_match(self):
612        """
613        Test mixed args and kwargs restriction allowed.
614
615        'test_user_func':
616            '*':
617                - 'my_mod.*':
618                    args:
619                        - 'a.*'
620                        - 'b.*'
621                    kwargs:
622                        'kwa': 'kwa.*'
623                        'kwb': 'kwb'
624        """
625        _check_minions_return = {"minions": ["some_minions"], "missing": []}
626        with patch(
627            "salt.utils.minions.CkMinions.check_minions",
628            MagicMock(return_value=_check_minions_return),
629        ):
630            self.valid_clear_load["kwargs"].update({"username": "test_user_func"})
631            self.valid_clear_load.update(
632                {
633                    "user": "test_user_func",
634                    "tgt": "*",
635                    "fun": "my_mod.some_func",
636                    "arg": [
637                        "alpha",
638                        "beta",
639                        "gamma",
640                        {
641                            "kwa": "kwarg #1",
642                            "kwb": "kwb",
643                            "one_more": "just one more",
644                            "__kwarg__": True,
645                        },
646                    ],
647                }
648            )
649            self.clear.publish(self.valid_clear_load)
650            self.assertEqual(
651                self.fire_event_mock.call_args[0][0]["fun"], "my_mod.some_func"
652            )
653
654    def test_args_mixed_mismatch(self):
655        """
656        Test mixed args and kwargs restriction forbidden.
657
658        'test_user_func':
659            '*':
660                - 'my_mod.*':
661                    args:
662                        - 'a.*'
663                        - 'b.*'
664                    kwargs:
665                        'kwa': 'kwa.*'
666                        'kwb': 'kwb'
667        """
668        _check_minions_return = {"minions": ["some_minions"], "missing": []}
669        with patch(
670            "salt.utils.minions.CkMinions.check_minions",
671            MagicMock(return_value=_check_minions_return),
672        ):
673            self.valid_clear_load["kwargs"].update({"username": "test_user_func"})
674            self.valid_clear_load.update(
675                {"user": "test_user_func", "tgt": "*", "fun": "my_mod.some_func"}
676            )
677            # Wrong arg value
678            self.valid_clear_load["arg"] = [
679                "alpha",
680                "gamma",
681                {
682                    "kwa": "kwarg #1",
683                    "kwb": "kwb",
684                    "one_more": "just one more",
685                    "__kwarg__": True,
686                },
687            ]
688            self.clear.publish(self.valid_clear_load)
689            self.assertEqual(self.fire_event_mock.mock_calls, [])
690            # Wrong kwarg value
691            self.valid_clear_load["arg"] = [
692                "alpha",
693                "beta",
694                "gamma",
695                {
696                    "kwa": "kkk",
697                    "kwb": "kwb",
698                    "one_more": "just one more",
699                    "__kwarg__": True,
700                },
701            ]
702            self.clear.publish(self.valid_clear_load)
703            self.assertEqual(self.fire_event_mock.mock_calls, [])
704            # Missing arg
705            self.valid_clear_load["arg"] = [
706                "alpha",
707                {
708                    "kwa": "kwarg #1",
709                    "kwb": "kwb",
710                    "one_more": "just one more",
711                    "__kwarg__": True,
712                },
713            ]
714            self.clear.publish(self.valid_clear_load)
715            self.assertEqual(self.fire_event_mock.mock_calls, [])
716            # Missing kwarg
717            self.valid_clear_load["arg"] = [
718                "alpha",
719                "beta",
720                "gamma",
721                {"kwa": "kwarg #1", "one_more": "just one more", "__kwarg__": True},
722            ]
723            self.clear.publish(self.valid_clear_load)
724            self.assertEqual(self.fire_event_mock.mock_calls, [])
725
726
727class AuthACLTestCase(ModuleCase):
728    """
729    A class to check various aspects of the publisher ACL system
730    """
731
732    def setUp(self):
733        self.auth_check_mock = MagicMock(return_value=True)
734        opts = self.get_temp_config("master")
735
736        patches = (
737            ("salt.minion.MasterMinion", MagicMock()),
738            ("salt.utils.verify.check_path_traversal", MagicMock()),
739            ("salt.utils.minions.CkMinions.auth_check", self.auth_check_mock),
740            ("salt.auth.LoadAuth.time_auth", MagicMock(return_value=True)),
741            ("salt.client.get_local_client", MagicMock()),
742        )
743        for mod, mock in patches:
744            patcher = patch(mod, mock)
745            patcher.start()
746            self.addCleanup(patcher.stop)
747        self.addCleanup(delattr, self, "auth_check_mock")
748
749        opts["publisher_acl"] = {}
750        opts["publisher_acl_blacklist"] = {}
751        opts["master_job_cache"] = ""
752        opts["sign_pub_messages"] = False
753        opts["con_cache"] = ""
754        opts["external_auth"] = {}
755        opts["external_auth"]["pam"] = {"test_user": [{"alpha_minion": ["test.ping"]}]}
756
757        self.clear = salt.master.ClearFuncs(opts, MagicMock())
758        self.addCleanup(self.clear.destroy)
759        self.addCleanup(delattr, self, "clear")
760
761        # overwrite the _send_pub method so we don't have to serialize MagicMock
762        self.clear._send_pub = lambda payload: True
763
764        # make sure to return a JID, instead of a mock
765        self.clear.mminion.returners = {".prep_jid": lambda x: 1}
766
767        self.valid_clear_load = {
768            "tgt_type": "glob",
769            "jid": "",
770            "cmd": "publish",
771            "tgt": "test_minion",
772            "kwargs": {
773                "username": "test_user",
774                "password": "test_password",
775                "show_timeout": False,
776                "eauth": "pam",
777                "show_jid": False,
778            },
779            "ret": "",
780            "user": "test_user",
781            "key": "",
782            "arg": "",
783            "fun": "test.ping",
784        }
785        self.addCleanup(delattr, self, "valid_clear_load")
786
787    @skipIf(salt.utils.platform.is_windows(), "PAM eauth not available on Windows")
788    def test_acl_simple_allow(self):
789        self.clear.publish(self.valid_clear_load)
790        self.assertEqual(
791            self.auth_check_mock.call_args[0][0], [{"alpha_minion": ["test.ping"]}]
792        )
793
794    def test_acl_simple_deny(self):
795        with patch(
796            "salt.auth.LoadAuth.get_auth_list",
797            MagicMock(return_value=[{"beta_minion": ["test.ping"]}]),
798        ):
799            self.clear.publish(self.valid_clear_load)
800            self.assertEqual(
801                self.auth_check_mock.call_args[0][0], [{"beta_minion": ["test.ping"]}]
802            )
803