# vim:set fileencoding=utf-8
#
# Copyright (C) 2015  Red Hat, Inc.
#
# This copyrighted material is made available to anyone wishing to use,
# modify, copy, or redistribute it subject to the terms and conditions of
# the GNU General Public License v.2, or (at your option) any later version.
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY expressed or implied, including the implied warranties of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
# Public License for more details.  You should have received a copy of the
# GNU General Public License along with this program; if not, write to the
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.  Any Red Hat trademarks that are incorporated in the
# source code or documentation are not subject to the GNU General Public
# License and may only be used or replicated with the express permission of
# Red Hat, Inc.
#

from pyanaconda import users
import unittest
import tempfile
import shutil
import os
import crypt
import platform
import glob

@unittest.skipIf(os.geteuid() != 0, "user creation must be run as root")
class UserCreateTest(unittest.TestCase):
    def setUp(self):
        self.users = users.Users()

        # Create a temporary directory with empty passwd and group files
        self.tmpdir = tempfile.mkdtemp()
        os.mkdir(self.tmpdir + "/etc")

        open(self.tmpdir + "/etc/passwd", "w").close()
        open(self.tmpdir + "/etc/group", "w").close()
        open(self.tmpdir + "/etc/shadow", "w").close()
        open(self.tmpdir + "/etc/gshadow", "w").close()

        # Copy over enough of libnss for UID and GID lookups to work
        with open(self.tmpdir + "/etc/nsswitch.conf", "w") as f:
            f.write("passwd: files\n")
            f.write("shadow: files\n")
            f.write("group: files\n")
            f.write("initgroups: files\n")
        if platform.architecture()[0].startswith("64"):
            libdir = "/lib64"
        else:
            libdir = "/lib"

        os.mkdir(self.tmpdir + libdir)
        for lib in glob.glob(libdir + "/libnss_files*"):
            shutil.copy(lib, self.tmpdir + lib)

    def tearDown(self):
        shutil.rmtree(self.tmpdir)

    def _readFields(self, filename, key):
        """Look for a line in a password or group file where the first field
           matches key, and return the record as a list of fields.
        """
        with open(self.tmpdir + filename) as f:
            for line in f:
                fields = line.strip().split(':')
                if fields[0] == key:
                    return fields
        return None

    def create_group_test(self):
        """Create a group."""
        self.users.createGroup("test_group", root=self.tmpdir)

        fields = self._readFields("/etc/group", "test_group")
        self.assertIsNotNone(fields)
        self.assertEqual(fields[0], "test_group")

        fields = self._readFields("/etc/gshadow", "test_group")
        self.assertIsNotNone(fields)
        self.assertEqual(fields[0], "test_group")

    def create_group_gid_test(self):
        """Create a group with a specific GID."""
        self.users.createGroup("test_group", gid=47, root=self.tmpdir)

        fields = self._readFields("/etc/group", "test_group")

        self.assertIsNotNone(fields)
        self.assertEqual(fields[0], "test_group")
        self.assertEqual(fields[2], "47")

    def create_group_exists_test(self):
        """Create a group that already exists."""
        with open(self.tmpdir + "/etc/group", "w") as f:
            f.write("test_group:x:47:\n")

        self.assertRaises(ValueError, self.users.createGroup, "test_group", root=self.tmpdir)

    def create_group_gid_exists_test(self):
        """Create a group with a GID that already exists."""
        with open(self.tmpdir + "/etc/group", "w") as f:
            f.write("gid_used:x:47:\n")

        self.assertRaises(ValueError, self.users.createGroup, "test_group", gid=47, root=self.tmpdir)

    def create_user_test(self):
        """Create a user."""
        self.users.createUser("test_user", root=self.tmpdir)

        pwd_fields = self._readFields("/etc/passwd", "test_user")
        self.assertIsNotNone(pwd_fields)
        self.assertEqual(pwd_fields[0], "test_user")

        # Check that the fields got the right default values
        # UID + GID set to some sort of int
        self.assertTrue(isinstance(int(pwd_fields[2]), int))
        self.assertTrue(isinstance(int(pwd_fields[3]), int))

        # home is /home/username
        self.assertEqual(pwd_fields[5], "/home/test_user")

        # shell set to something
        self.assertTrue(pwd_fields[6])

        shadow_fields = self._readFields("/etc/shadow", "test_user")
        self.assertIsNotNone(shadow_fields)
        self.assertEqual(shadow_fields[0], "test_user")

        # Ensure the password is locked
        self.assertTrue(shadow_fields[1].startswith("!"))

        # Ensure the date of last password change is empty
        self.assertEqual(shadow_fields[2], "")

        # Check that the user group was created
        grp_fields = self._readFields("/etc/group", "test_user")
        self.assertIsNotNone(grp_fields)
        self.assertEqual(grp_fields[0], "test_user")

        # Check that user group's GID matches the user's GID
        self.assertEqual(grp_fields[2], pwd_fields[3])

        gshadow_fields = self._readFields("/etc/gshadow", "test_user")
        self.assertIsNotNone(gshadow_fields)
        self.assertEqual(gshadow_fields[0], "test_user")

    def create_user_text_options_test(self):
        """Create a user with the text fields set."""
        self.users.createUser("test_user", gecos="Test User", homedir="/home/users/testuser", shell="/bin/test", root=self.tmpdir)

        pwd_fields = self._readFields("/etc/passwd", "test_user")
        self.assertIsNotNone(pwd_fields)
        self.assertEqual(pwd_fields[0], "test_user")
        self.assertEqual(pwd_fields[4], "Test User")
        self.assertEqual(pwd_fields[5], "/home/users/testuser")
        self.assertEqual(pwd_fields[6], "/bin/test")

        # Check that the home directory was created
        self.assertTrue(os.path.isdir(self.tmpdir + "/home/users/testuser"))

    def create_user_groups_test(self):
        """Create a user with a list of groups."""
        # Create one of the groups
        self.users.createGroup("test3", root=self.tmpdir)

        # Create a user and add it three groups, two of which do not exist,
        # and one which specifies a GID.
        self.users.createUser("test_user", groups=["test1", "test2(5001)", "test3"], root=self.tmpdir)

        grp_fields1 = self._readFields("/etc/group", "test1")
        self.assertEqual(grp_fields1[3], "test_user")

        grp_fields2 = self._readFields("/etc/group", "test2")
        self.assertEqual(grp_fields2[3], "test_user")
        self.assertEqual(grp_fields2[2], "5001")

        grp_fields3 = self._readFields("/etc/group", "test3")
        self.assertEqual(grp_fields3[3], "test_user")

    def create_user_groups_gid_conflict_test(self):
        """Create a user with a bad list of groups."""
        # Create one of the groups
        self.users.createGroup("test3", gid=5000, root=self.tmpdir)

        # Add test3 to the group list with a different GID.
        self.assertRaises(ValueError, self.users.createUser,
                "test_user", groups=["test3(5002)"], root=self.tmpdir)

    def create_user_password_test(self):
        """Create a user with a password."""

        self.users.createUser("test_user1", password="password", root=self.tmpdir)
        shadow_fields = self._readFields("/etc/shadow", "test_user1")
        self.assertIsNotNone(shadow_fields)
        # Make sure the password works
        self.assertEqual(crypt.crypt("password", shadow_fields[1]), shadow_fields[1])

        # Set the encrypted password for another user with isCrypted
        cryptpw = shadow_fields[1]
        self.users.createUser("test_user2", password=cryptpw, isCrypted=True, root=self.tmpdir)
        shadow_fields = self._readFields("/etc/shadow", "test_user2")
        self.assertIsNotNone(shadow_fields)
        self.assertEqual(cryptpw, shadow_fields[1])

        # Set an empty password
        self.users.createUser("test_user3", password="", root=self.tmpdir)
        shadow_fields = self._readFields("/etc/shadow", "test_user3")
        self.assertIsNotNone(shadow_fields)
        self.assertEqual("", shadow_fields[1])

    def create_user_lock_test(self):
        """Create a locked user account."""

        # Create an empty, locked password
        self.users.createUser("test_user1", lock=True, password="", root=self.tmpdir)
        shadow_fields = self._readFields("/etc/shadow", "test_user1")
        self.assertIsNotNone(shadow_fields)
        self.assertEqual("!", shadow_fields[1])

        # Create a locked password and ensure it can be unlocked (by removing the ! at the front)
        self.users.createUser("test_user2", lock=True, password="password", root=self.tmpdir)
        shadow_fields = self._readFields("/etc/shadow", "test_user2")
        self.assertIsNotNone(shadow_fields)
        self.assertTrue(shadow_fields[1].startswith("!"))
        self.assertEqual(crypt.crypt("password", shadow_fields[1][1:]), shadow_fields[1][1:])

    def create_user_uid_test(self):
        """Create a user with a specific UID."""

        self.users.createUser("test_user", uid=1047, root=self.tmpdir)
        pwd_fields = self._readFields("/etc/passwd", "test_user")
        self.assertIsNotNone(pwd_fields)
        self.assertEqual(pwd_fields[2], "1047")

    def create_user_gid_test(self):
        """Create a user with a specific GID."""

        self.users.createUser("test_user", gid=1047, root=self.tmpdir)

        pwd_fields = self._readFields("/etc/passwd", "test_user")
        self.assertIsNotNone(pwd_fields)
        self.assertEqual(pwd_fields[3], "1047")

        grp_fields = self._readFields("/etc/group", "test_user")
        self.assertIsNotNone(grp_fields)
        self.assertEqual(grp_fields[2], "1047")

    def create_user_algo_test(self):
        """Create a user with a specific password algorithm."""

        self.users.createUser("test_user1", password="password", algo="md5", root=self.tmpdir)
        shadow_fields = self._readFields("/etc/shadow", "test_user1")
        self.assertIsNotNone(shadow_fields)
        self.assertTrue(shadow_fields[1].startswith("$1$"))

        self.users.createUser("test_user2", password="password", algo="sha512", root=self.tmpdir)
        shadow_fields = self._readFields("/etc/shadow", "test_user2")
        self.assertIsNotNone(shadow_fields)
        self.assertTrue(shadow_fields[1].startswith("$6$"))

    def create_user_exists_test(self):
        """Create a user that already exists."""
        with open(self.tmpdir + "/etc/passwd", "w") as f:
            f.write("test_user:x:1000:1000::/:/bin/sh\n")

        self.assertRaises(ValueError, self.users.createUser, "test_user", root=self.tmpdir)

    def create_user_uid_exists_test(self):
        """Create a user with a UID that already exists."""
        with open(self.tmpdir + "/etc/passwd", "w") as f:
            f.write("conflict:x:1000:1000::/:/bin/sh\n")

        self.assertRaises(ValueError, self.users.createUser, "test_user", uid=1000, root=self.tmpdir)

    def create_user_gid_exists_test(self):
        """Create a user with a GID of an existing group."""
        self.users.createGroup("test_group", gid=5000, root=self.tmpdir)
        self.users.createUser("test_user", gid=5000, root=self.tmpdir)

        passwd_fields = self._readFields("/etc/passwd", "test_user")
        self.assertIsNotNone(passwd_fields)
        self.assertEqual(passwd_fields[3], "5000")

    def set_user_ssh_key_test(self):
        keydata = "THIS IS TOTALLY A SSH KEY"

        self.users.createUser("test_user", homedir="/home/test_user", root=self.tmpdir)
        self.users.setUserSshKey("test_user", keydata, root=self.tmpdir)

        keyfile = self.tmpdir + "/home/test_user/.ssh/authorized_keys"
        self.assertTrue(os.path.isfile(keyfile))
        with open(keyfile) as f:
            output_keydata = f.read()

        self.assertEqual(keydata, output_keydata.strip())

    def set_root_password_test(self):
        password = "password1"

        # Initialize a root user with an empty password, like the setup package would have
        with open(self.tmpdir + "/etc/passwd", "w") as f:
            f.write("root:x:0:0:root:/root:/bin/bash\n")

        with open(self.tmpdir + "/etc/shadow", "w") as f:
            f.write("root:*:16489:0:99999:7:::\n")

        self.users.setRootPassword(password, root=self.tmpdir)
        shadow_fields = self._readFields("/etc/shadow", "root")
        self.assertEqual(crypt.crypt(password, shadow_fields[1]), shadow_fields[1])

        # Try a different password with isLocked=True
        password = "password2"
        self.users.setRootPassword(password, isLocked=True, root=self.tmpdir)
        shadow_fields = self._readFields("/etc/shadow", "root")
        self.assertTrue(shadow_fields[1].startswith("!"))
        self.assertEqual(crypt.crypt(password, shadow_fields[1][1:]), shadow_fields[1][1:])

        # Try an encrypted password
        password = "$1$asdf$password"
        self.users.setRootPassword(password, isCrypted=True, root=self.tmpdir)
        shadow_fields = self._readFields("/etc/shadow", "root")
        self.assertEqual(password, shadow_fields[1])

    def create_user_reuse_home_test(self):
        # Create a user, reusing an old home directory

        os.makedirs(self.tmpdir + "/home/test_user")
        os.chown(self.tmpdir + "/home/test_user", 500, 500)

        self.users.createUser("test_user", homedir="/home/test_user", uid=1000, gid=1000, root=self.tmpdir)
        passwd_fields = self._readFields("/etc/passwd", "test_user")
        self.assertIsNotNone(passwd_fields)
        self.assertEqual(passwd_fields[2], "1000")
        self.assertEqual(passwd_fields[3], "1000")

        stat_fields = os.stat(self.tmpdir + "/home/test_user")
        self.assertEqual(stat_fields.st_uid, 1000)
        self.assertEqual(stat_fields.st_gid, 1000)

    def create_user_gid_in_group_list_test(self):
        """Create a user with a GID equal to that of one of the requested groups"""

        self.users.createUser("test_user", gid=1047, groups=["test_group(1047)"], root=self.tmpdir)

        # Ensure that the user's GID is equal to the GID requested
        pwd_fields = self._readFields("/etc/passwd", "test_user")
        self.assertIsNotNone(pwd_fields)
        self.assertEqual(pwd_fields[3], "1047")

        # and that the requested group has the right GID
        grp_fields = self._readFields("/etc/group", "test_group")
        self.assertIsNotNone(grp_fields)
        self.assertEqual(grp_fields[2], "1047")
