# -*- coding: utf-8 -*-

from __future__ import absolute_import

import salt.config
import salt.master
from tests.support.helpers import slowTest
from tests.support.mock import MagicMock, patch
from tests.support.unit import TestCase


class TransportMethodsTest(TestCase):
    def test_transport_methods(self):
        class Foo(salt.master.TransportMethods):
            expose_methods = ["bar"]

            def bar(self):
                pass

            def bang(self):
                pass

        foo = Foo()
        assert foo.get_method("bar") is not None
        assert foo.get_method("bang") is None

    def test_aes_funcs_white(self):
        """
        Validate methods exposed on AESFuncs exist and are callable
        """
        opts = salt.config.master_config(None)
        aes_funcs = salt.master.AESFuncs(opts)
        for name in aes_funcs.expose_methods:
            func = getattr(aes_funcs, name, None)
            assert callable(func)

    def test_aes_funcs_black(self):
        """
        Validate methods on AESFuncs that should not be called remotely
        """
        opts = salt.config.master_config(None)
        aes_funcs = salt.master.AESFuncs(opts)
        # Any callable that should not explicitly be allowed should be added
        # here.
        blacklist_methods = [
            "_AESFuncs__setup_fileserver",
            "_AESFuncs__verify_load",
            "_AESFuncs__verify_minion",
            "_AESFuncs__verify_minion_publish",
            "__class__",
            "__delattr__",
            "__dir__",
            "__eq__",
            "__format__",
            "__ge__",
            "__getattribute__",
            "__gt__",
            "__hash__",
            "__init__",
            "__init_subclass__",
            "__le__",
            "__lt__",
            "__ne__",
            "__new__",
            "__reduce__",
            "__reduce_ex__",
            "__repr__",
            "__setattr__",
            "__sizeof__",
            "__str__",
            "__subclasshook__",
            "get_method",
            "run_func",
        ]
        for name in dir(aes_funcs):
            if name in aes_funcs.expose_methods:
                continue
            if not callable(getattr(aes_funcs, name)):
                continue
            assert name in blacklist_methods, name

    def test_clear_funcs_white(self):
        """
        Validate methods exposed on ClearFuncs exist and are callable
        """
        opts = salt.config.master_config(None)
        clear_funcs = salt.master.ClearFuncs(opts, {})
        for name in clear_funcs.expose_methods:
            func = getattr(clear_funcs, name, None)
            assert callable(func)

    def test_clear_funcs_black(self):
        """
        Validate methods on ClearFuncs that should not be called remotely
        """
        opts = salt.config.master_config(None)
        clear_funcs = salt.master.ClearFuncs(opts, {})
        blacklist_methods = [
            "__class__",
            "__delattr__",
            "__dir__",
            "__eq__",
            "__format__",
            "__ge__",
            "__getattribute__",
            "__gt__",
            "__hash__",
            "__init__",
            "__init_subclass__",
            "__le__",
            "__lt__",
            "__ne__",
            "__new__",
            "__reduce__",
            "__reduce_ex__",
            "__repr__",
            "__setattr__",
            "__sizeof__",
            "__str__",
            "__subclasshook__",
            "_prep_auth_info",
            "_prep_jid",
            "_prep_pub",
            "_send_pub",
            "_send_ssh_pub",
            "get_method",
        ]
        for name in dir(clear_funcs):
            if name in clear_funcs.expose_methods:
                continue
            if not callable(getattr(clear_funcs, name)):
                continue
            assert name in blacklist_methods, name


class ClearFuncsTestCase(TestCase):
    """
    TestCase for salt.master.ClearFuncs class
    """

    @classmethod
    def setUpClass(cls):
        opts = salt.config.master_config(None)
        cls.clear_funcs = salt.master.ClearFuncs(opts, {})

    @classmethod
    def tearDownClass(cls):
        del cls.clear_funcs

    def test_get_method(self):
        assert getattr(self.clear_funcs, "_send_pub", None) is not None
        assert self.clear_funcs.get_method("_send_pub") is None

    # runner tests

    @slowTest
    def test_runner_token_not_authenticated(self):
        """
        Asserts that a TokenAuthenticationError is returned when the token can't authenticate.
        """
        mock_ret = {
            "error": {
                "name": "TokenAuthenticationError",
                "message": 'Authentication failure of type "token" occurred.',
            }
        }
        ret = self.clear_funcs.runner({"token": "asdfasdfasdfasdf"})
        self.assertDictEqual(mock_ret, ret)

    @slowTest
    def test_runner_token_authorization_error(self):
        """
        Asserts that a TokenAuthenticationError is returned when the token authenticates, but is
        not authorized.
        """
        token = "asdfasdfasdfasdf"
        clear_load = {"token": token, "fun": "test.arg"}
        mock_token = {"token": token, "eauth": "foo", "name": "test"}
        mock_ret = {
            "error": {
                "name": "TokenAuthenticationError",
                "message": 'Authentication failure of type "token" occurred '
                "for user test.",
            }
        }

        with patch(
            "salt.auth.LoadAuth.authenticate_token", MagicMock(return_value=mock_token)
        ), patch("salt.auth.LoadAuth.get_auth_list", MagicMock(return_value=[])):
            ret = self.clear_funcs.runner(clear_load)

        self.assertDictEqual(mock_ret, ret)

    @slowTest
    def test_runner_token_salt_invocation_error(self):
        """
        Asserts that a SaltInvocationError is returned when the token authenticates, but the
        command is malformed.
        """
        token = "asdfasdfasdfasdf"
        clear_load = {"token": token, "fun": "badtestarg"}
        mock_token = {"token": token, "eauth": "foo", "name": "test"}
        mock_ret = {
            "error": {
                "name": "SaltInvocationError",
                "message": "A command invocation error occurred: Check syntax.",
            }
        }

        with patch(
            "salt.auth.LoadAuth.authenticate_token", MagicMock(return_value=mock_token)
        ), patch(
            "salt.auth.LoadAuth.get_auth_list", MagicMock(return_value=["testing"])
        ):
            ret = self.clear_funcs.runner(clear_load)

        self.assertDictEqual(mock_ret, ret)

    @slowTest
    def test_runner_eauth_not_authenticated(self):
        """
        Asserts that an EauthAuthenticationError is returned when the user can't authenticate.
        """
        mock_ret = {
            "error": {
                "name": "EauthAuthenticationError",
                "message": 'Authentication failure of type "eauth" occurred for '
                "user UNKNOWN.",
            }
        }
        ret = self.clear_funcs.runner({"eauth": "foo"})
        self.assertDictEqual(mock_ret, ret)

    @slowTest
    def test_runner_eauth_authorization_error(self):
        """
        Asserts that an EauthAuthenticationError is returned when the user authenticates, but is
        not authorized.
        """
        clear_load = {"eauth": "foo", "username": "test", "fun": "test.arg"}
        mock_ret = {
            "error": {
                "name": "EauthAuthenticationError",
                "message": 'Authentication failure of type "eauth" occurred for '
                "user test.",
            }
        }
        with patch(
            "salt.auth.LoadAuth.authenticate_eauth", MagicMock(return_value=True)
        ), patch("salt.auth.LoadAuth.get_auth_list", MagicMock(return_value=[])):
            ret = self.clear_funcs.runner(clear_load)

        self.assertDictEqual(mock_ret, ret)

    @slowTest
    def test_runner_eauth_salt_invocation_error(self):
        """
        Asserts that an EauthAuthenticationError is returned when the user authenticates, but the
        command is malformed.
        """
        clear_load = {"eauth": "foo", "username": "test", "fun": "bad.test.arg.func"}
        mock_ret = {
            "error": {
                "name": "SaltInvocationError",
                "message": "A command invocation error occurred: Check syntax.",
            }
        }
        with patch(
            "salt.auth.LoadAuth.authenticate_eauth", MagicMock(return_value=True)
        ), patch(
            "salt.auth.LoadAuth.get_auth_list", MagicMock(return_value=["testing"])
        ):
            ret = self.clear_funcs.runner(clear_load)

        self.assertDictEqual(mock_ret, ret)

    @slowTest
    def test_runner_user_not_authenticated(self):
        """
        Asserts that an UserAuthenticationError is returned when the user can't authenticate.
        """
        mock_ret = {
            "error": {
                "name": "UserAuthenticationError",
                "message": 'Authentication failure of type "user" occurred',
            }
        }
        ret = self.clear_funcs.runner({})
        self.assertDictEqual(mock_ret, ret)

    # wheel tests

    @slowTest
    def test_wheel_token_not_authenticated(self):
        """
        Asserts that a TokenAuthenticationError is returned when the token can't authenticate.
        """
        mock_ret = {
            "error": {
                "name": "TokenAuthenticationError",
                "message": 'Authentication failure of type "token" occurred.',
            }
        }
        ret = self.clear_funcs.wheel({"token": "asdfasdfasdfasdf"})
        self.assertDictEqual(mock_ret, ret)

    @slowTest
    def test_wheel_token_authorization_error(self):
        """
        Asserts that a TokenAuthenticationError is returned when the token authenticates, but is
        not authorized.
        """
        token = "asdfasdfasdfasdf"
        clear_load = {"token": token, "fun": "test.arg"}
        mock_token = {"token": token, "eauth": "foo", "name": "test"}
        mock_ret = {
            "error": {
                "name": "TokenAuthenticationError",
                "message": 'Authentication failure of type "token" occurred '
                "for user test.",
            }
        }

        with patch(
            "salt.auth.LoadAuth.authenticate_token", MagicMock(return_value=mock_token)
        ), patch("salt.auth.LoadAuth.get_auth_list", MagicMock(return_value=[])):
            ret = self.clear_funcs.wheel(clear_load)

        self.assertDictEqual(mock_ret, ret)

    @slowTest
    def test_wheel_token_salt_invocation_error(self):
        """
        Asserts that a SaltInvocationError is returned when the token authenticates, but the
        command is malformed.
        """
        token = "asdfasdfasdfasdf"
        clear_load = {"token": token, "fun": "badtestarg"}
        mock_token = {"token": token, "eauth": "foo", "name": "test"}
        mock_ret = {
            "error": {
                "name": "SaltInvocationError",
                "message": "A command invocation error occurred: Check syntax.",
            }
        }

        with patch(
            "salt.auth.LoadAuth.authenticate_token", MagicMock(return_value=mock_token)
        ), patch(
            "salt.auth.LoadAuth.get_auth_list", MagicMock(return_value=["testing"])
        ):
            ret = self.clear_funcs.wheel(clear_load)

        self.assertDictEqual(mock_ret, ret)

    @slowTest
    def test_wheel_eauth_not_authenticated(self):
        """
        Asserts that an EauthAuthenticationError is returned when the user can't authenticate.
        """
        mock_ret = {
            "error": {
                "name": "EauthAuthenticationError",
                "message": 'Authentication failure of type "eauth" occurred for '
                "user UNKNOWN.",
            }
        }
        ret = self.clear_funcs.wheel({"eauth": "foo"})
        self.assertDictEqual(mock_ret, ret)

    @slowTest
    def test_wheel_eauth_authorization_error(self):
        """
        Asserts that an EauthAuthenticationError is returned when the user authenticates, but is
        not authorized.
        """
        clear_load = {"eauth": "foo", "username": "test", "fun": "test.arg"}
        mock_ret = {
            "error": {
                "name": "EauthAuthenticationError",
                "message": 'Authentication failure of type "eauth" occurred for '
                "user test.",
            }
        }
        with patch(
            "salt.auth.LoadAuth.authenticate_eauth", MagicMock(return_value=True)
        ), patch("salt.auth.LoadAuth.get_auth_list", MagicMock(return_value=[])):
            ret = self.clear_funcs.wheel(clear_load)

        self.assertDictEqual(mock_ret, ret)

    @slowTest
    def test_wheel_eauth_salt_invocation_error(self):
        """
        Asserts that an EauthAuthenticationError is returned when the user authenticates, but the
        command is malformed.
        """
        clear_load = {"eauth": "foo", "username": "test", "fun": "bad.test.arg.func"}
        mock_ret = {
            "error": {
                "name": "SaltInvocationError",
                "message": "A command invocation error occurred: Check syntax.",
            }
        }
        with patch(
            "salt.auth.LoadAuth.authenticate_eauth", MagicMock(return_value=True)
        ), patch(
            "salt.auth.LoadAuth.get_auth_list", MagicMock(return_value=["testing"])
        ):
            ret = self.clear_funcs.wheel(clear_load)

        self.assertDictEqual(mock_ret, ret)

    @slowTest
    def test_wheel_user_not_authenticated(self):
        """
        Asserts that an UserAuthenticationError is returned when the user can't authenticate.
        """
        mock_ret = {
            "error": {
                "name": "UserAuthenticationError",
                "message": 'Authentication failure of type "user" occurred',
            }
        }
        ret = self.clear_funcs.wheel({})
        self.assertDictEqual(mock_ret, ret)

    # publish tests

    @slowTest
    def test_publish_user_is_blacklisted(self):
        """
        Asserts that an AuthorizationError is returned when the user has been blacklisted.
        """
        mock_ret = {
            "error": {
                "name": "AuthorizationError",
                "message": "Authorization error occurred.",
            }
        }
        with patch(
            "salt.acl.PublisherACL.user_is_blacklisted", MagicMock(return_value=True)
        ):
            self.assertEqual(
                mock_ret, self.clear_funcs.publish({"user": "foo", "fun": "test.arg"})
            )

    @slowTest
    def test_publish_cmd_blacklisted(self):
        """
        Asserts that an AuthorizationError is returned when the command has been blacklisted.
        """
        mock_ret = {
            "error": {
                "name": "AuthorizationError",
                "message": "Authorization error occurred.",
            }
        }
        with patch(
            "salt.acl.PublisherACL.user_is_blacklisted", MagicMock(return_value=False)
        ), patch(
            "salt.acl.PublisherACL.cmd_is_blacklisted", MagicMock(return_value=True)
        ):
            self.assertEqual(
                mock_ret, self.clear_funcs.publish({"user": "foo", "fun": "test.arg"})
            )

    @slowTest
    def test_publish_token_not_authenticated(self):
        """
        Asserts that an AuthenticationError is returned when the token can't authenticate.
        """
        mock_ret = {
            "error": {
                "name": "AuthenticationError",
                "message": "Authentication error occurred.",
            }
        }
        load = {
            "user": "foo",
            "fun": "test.arg",
            "tgt": "test_minion",
            "kwargs": {"token": "asdfasdfasdfasdf"},
        }
        with patch(
            "salt.acl.PublisherACL.user_is_blacklisted", MagicMock(return_value=False)
        ), patch(
            "salt.acl.PublisherACL.cmd_is_blacklisted", MagicMock(return_value=False)
        ):
            self.assertEqual(mock_ret, self.clear_funcs.publish(load))

    @slowTest
    def test_publish_token_authorization_error(self):
        """
        Asserts that an AuthorizationError is returned when the token authenticates, but is not
        authorized.
        """
        token = "asdfasdfasdfasdf"
        load = {
            "user": "foo",
            "fun": "test.arg",
            "tgt": "test_minion",
            "arg": "bar",
            "kwargs": {"token": token},
        }
        mock_token = {"token": token, "eauth": "foo", "name": "test"}
        mock_ret = {
            "error": {
                "name": "AuthorizationError",
                "message": "Authorization error occurred.",
            }
        }

        with patch(
            "salt.acl.PublisherACL.user_is_blacklisted", MagicMock(return_value=False)
        ), patch(
            "salt.acl.PublisherACL.cmd_is_blacklisted", MagicMock(return_value=False)
        ), patch(
            "salt.auth.LoadAuth.authenticate_token", MagicMock(return_value=mock_token)
        ), patch(
            "salt.auth.LoadAuth.get_auth_list", MagicMock(return_value=[])
        ):
            self.assertEqual(mock_ret, self.clear_funcs.publish(load))

    @slowTest
    def test_publish_eauth_not_authenticated(self):
        """
        Asserts that an AuthenticationError is returned when the user can't authenticate.
        """
        load = {
            "user": "test",
            "fun": "test.arg",
            "tgt": "test_minion",
            "kwargs": {"eauth": "foo"},
        }
        mock_ret = {
            "error": {
                "name": "AuthenticationError",
                "message": "Authentication error occurred.",
            }
        }
        with patch(
            "salt.acl.PublisherACL.user_is_blacklisted", MagicMock(return_value=False)
        ), patch(
            "salt.acl.PublisherACL.cmd_is_blacklisted", MagicMock(return_value=False)
        ):
            self.assertEqual(mock_ret, self.clear_funcs.publish(load))

    @slowTest
    def test_publish_eauth_authorization_error(self):
        """
        Asserts that an AuthorizationError is returned when the user authenticates, but is not
        authorized.
        """
        load = {
            "user": "test",
            "fun": "test.arg",
            "tgt": "test_minion",
            "kwargs": {"eauth": "foo"},
            "arg": "bar",
        }
        mock_ret = {
            "error": {
                "name": "AuthorizationError",
                "message": "Authorization error occurred.",
            }
        }
        with patch(
            "salt.acl.PublisherACL.user_is_blacklisted", MagicMock(return_value=False)
        ), patch(
            "salt.acl.PublisherACL.cmd_is_blacklisted", MagicMock(return_value=False)
        ), patch(
            "salt.auth.LoadAuth.authenticate_eauth", MagicMock(return_value=True)
        ), patch(
            "salt.auth.LoadAuth.get_auth_list", MagicMock(return_value=[])
        ):
            self.assertEqual(mock_ret, self.clear_funcs.publish(load))

    @slowTest
    def test_publish_user_not_authenticated(self):
        """
        Asserts that an AuthenticationError is returned when the user can't authenticate.
        """
        load = {"user": "test", "fun": "test.arg", "tgt": "test_minion"}
        mock_ret = {
            "error": {
                "name": "AuthenticationError",
                "message": "Authentication error occurred.",
            }
        }
        with patch(
            "salt.acl.PublisherACL.user_is_blacklisted", MagicMock(return_value=False)
        ), patch(
            "salt.acl.PublisherACL.cmd_is_blacklisted", MagicMock(return_value=False)
        ):
            self.assertEqual(mock_ret, self.clear_funcs.publish(load))

    @slowTest
    def test_publish_user_authenticated_missing_auth_list(self):
        """
        Asserts that an AuthenticationError is returned when the user has an effective user id and is
        authenticated, but the auth_list is empty.
        """
        load = {
            "user": "test",
            "fun": "test.arg",
            "tgt": "test_minion",
            "kwargs": {"user": "test"},
            "arg": "foo",
        }
        mock_ret = {
            "error": {
                "name": "AuthenticationError",
                "message": "Authentication error occurred.",
            }
        }
        with patch(
            "salt.acl.PublisherACL.user_is_blacklisted", MagicMock(return_value=False)
        ), patch(
            "salt.acl.PublisherACL.cmd_is_blacklisted", MagicMock(return_value=False)
        ), patch(
            "salt.auth.LoadAuth.authenticate_key",
            MagicMock(return_value="fake-user-key"),
        ), patch(
            "salt.utils.master.get_values_of_matching_keys", MagicMock(return_value=[])
        ):
            self.assertEqual(mock_ret, self.clear_funcs.publish(load))

    @slowTest
    def test_publish_user_authorization_error(self):
        """
        Asserts that an AuthorizationError is returned when the user authenticates, but is not
        authorized.
        """
        load = {
            "user": "test",
            "fun": "test.arg",
            "tgt": "test_minion",
            "kwargs": {"user": "test"},
            "arg": "foo",
        }
        mock_ret = {
            "error": {
                "name": "AuthorizationError",
                "message": "Authorization error occurred.",
            }
        }
        with patch(
            "salt.acl.PublisherACL.user_is_blacklisted", MagicMock(return_value=False)
        ), patch(
            "salt.acl.PublisherACL.cmd_is_blacklisted", MagicMock(return_value=False)
        ), patch(
            "salt.auth.LoadAuth.authenticate_key",
            MagicMock(return_value="fake-user-key"),
        ), patch(
            "salt.utils.master.get_values_of_matching_keys",
            MagicMock(return_value=["test"]),
        ), patch(
            "salt.utils.minions.CkMinions.auth_check", MagicMock(return_value=False)
        ):
            self.assertEqual(mock_ret, self.clear_funcs.publish(load))
