# -*- coding: utf-8 -*-
"""
    tests.integration.shell.auth
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
"""

from __future__ import absolute_import, print_function, unicode_literals

import logging
import random
import string

import pytest
import salt.utils.platform
from salt.ext.six.moves import range
from salt.utils.pycrypto import gen_hash
from tests.support.case import ModuleCase, ShellCase
from tests.support.helpers import (
    destructiveTest,
    requires_salt_modules,
    requires_salt_states,
    skip_if_not_root,
    slowTest,
)
from tests.support.mixins import SaltReturnAssertsMixin
from tests.support.unit import skipIf

try:
    import pwd
    import grp
except ImportError:
    pwd, grp = None, None


log = logging.getLogger(__name__)


def gen_password():
    """
    generate a password and hash it
    """
    password = "".join(
        random.choice(string.ascii_letters + string.digits) for _ in range(20)
    )
    hashed_pwd = (
        password
        if salt.utils.platform.is_darwin()
        else gen_hash("salt", password, "sha512")
    )

    return password, hashed_pwd


@requires_salt_states("user.absent", "user.present")
@requires_salt_modules("shadow.set_password")
@skip_if_not_root
@skipIf(pwd is None or grp is None, "No pwd or grp module available")
@destructiveTest
@pytest.mark.windows_whitelisted
class UserAuthTest(ModuleCase, SaltReturnAssertsMixin, ShellCase):
    """
    Test user auth mechanisms
    """

    _call_binary_ = "salt"
    user = "saltdev"

    def setUp(self):
        ret = self.run_state("user.present", name=self.user, createhome=False)
        self.assertSaltTrueReturn(ret)

    def tearDown(self):
        ret = self.run_state("user.absent", name=self.user)
        self.assertSaltTrueReturn(ret)

    @slowTest
    def test_pam_auth_valid_user(self):
        """
        test that pam auth mechanism works with a valid user
        """
        password, hashed_pwd = gen_password()

        # set user password
        set_pw_cmd = "shadow.set_password {0} '{1}'".format(self.user, hashed_pwd)
        stdout, stderr, retcode = self.run_call(
            set_pw_cmd, catch_stderr=True, with_retcode=True
        )
        if stderr:
            log.warning(stderr)
        self.assertFalse(retcode, stderr)

        # test user auth against pam
        cmd = '-a pam "*" test.ping --username {0} --password {1}'.format(
            self.user, password
        )
        resp = self.run_salt(cmd)
        log.debug("resp = %s", resp)
        self.assertIn("minion", [r.strip(": ") for r in resp])

    @slowTest
    def test_pam_auth_invalid_user(self):
        """
        test pam auth mechanism errors for an invalid user
        """
        cmd = '-a pam "*" test.ping ' "--username nouser --password {0}".format(
            "abcd1234"
        )
        resp = self.run_salt(cmd)
        self.assertIn("Authentication error occurred", "".join(resp))


@requires_salt_states("group.absent", "group.present", "user.absent", "user.present")
@requires_salt_modules("shadow.set_password", "user.chgroups")
@skip_if_not_root
@skipIf(pwd is None or grp is None, "No crypt module available")
@destructiveTest
class GroupAuthTest(ModuleCase, SaltReturnAssertsMixin, ShellCase):
    """
    Test group auth mechanisms
    """

    _call_binary_ = "salt"

    user = "saltadm"
    group = "saltops"

    def setUp(self):
        ret = self.run_state("group.present", name=self.group)
        self.assertSaltTrueReturn(ret)
        ret = self.run_state(
            "user.present", name=self.user, createhome=False, groups=[self.group]
        )
        self.assertSaltTrueReturn(ret)
        stdout, stderr, retcode = self.run_call(
            "user.chgroups {0} {1} True".format(self.user, self.group),
            local=True,
            with_retcode=True,
            catch_stderr=True,
        )
        if stderr:
            log.warning(stderr)
        self.assertFalse(retcode, stderr)

    def tearDown(self):
        ret0 = self.run_state("user.absent", name=self.user)
        ret1 = self.run_state("group.absent", name=self.group)
        self.assertSaltTrueReturn(ret0)
        self.assertSaltTrueReturn(ret1)

    @slowTest
    def test_pam_auth_valid_group(self):
        """
        test that pam auth mechanism works for a valid group
        """
        password, hashed_pwd = gen_password()

        # set user password
        set_pw_cmd = "shadow.set_password {0} '{1}'".format(self.user, hashed_pwd)
        stdout, stderr, retcode = self.run_call(
            set_pw_cmd, catch_stderr=True, with_retcode=True
        )
        if stderr:
            log.warning(stderr)
        self.assertFalse(retcode, stderr)

        # test group auth against pam: saltadm is not configured in
        # external_auth, but saltops is and saldadm is a member of saltops
        cmd = '-a pam "*" test.ping --username {0} --password {1}'.format(
            self.user, password
        )
        resp = self.run_salt(cmd)
        self.assertIn("minion", [r.strip(": ") for r in resp])
