/******************************************************************************
 * Copyright (c) Huawei Technologies Co., Ltd. 2020-2020. All rights reserved.
 * iSulad licensed under the Mulan PSL v1.
 * You can use this software according to the terms and conditions of the Mulan PSL v1.
 * You may obtain a copy of Mulan PSL v1 at:
 *     http://license.coscl.org.cn/MulanPSL
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
 * PURPOSE.
 * See the Mulan PSL v1 for more details.
 * Author: wujing
 * Create: 2020-02-14
 * Description: provide selinux label unit test
 ******************************************************************************/

#include "selinux_label.h"
#include <iostream>
#include <algorithm>
#include <tuple>
#include <fstream>
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <selinux/selinux.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "namespace_mock.h"
#include "utils.h"

using namespace std;

class SELinuxLabelUnitTest : public testing::Test {
protected:
    void SetUp() override
    {
        selinux_state_init();
    }
    void TearDown() override
    {
        std::cout << "selinux_state is the resident memory of the daemon." <<
                  " The process exits and the memory is automatically released." << std::endl;
    }
};

TEST_F(SELinuxLabelUnitTest, test_init_label_normal)
{
    const char *disable_label[] = { "disable" };
    const char *user_label[] = { "user:fakeuser" };
    const char *role_label[] = { "role:fakerole" };
    const char *type_label[] = { "type:faketype" };
    const char *level_label[] = { "level:s0:c1,c2" };
    const char *full_label[] =  { "user:fakeuser", "level:s0:c1,c2", "type:faketype", "role:fakerole" };

    std::vector<std::tuple<const char **, size_t, int, std::string, std::string>> normal {
        std::make_tuple(disable_label, 1, 0, "", ""),
        std::make_tuple(user_label, 1, 0, "fakeuser:system_r:container_t:s0", "fakeuser:object_r:container_file_t:s0"),
        std::make_tuple(role_label, 1, 0, "system_u:fakerole:container_t:s0", "system_u:object_r:container_file_t:s0"),
        std::make_tuple(type_label, 1, 0, "system_u:system_r:faketype:s0", "system_u:object_r:container_file_t:s0"),
        std::make_tuple(level_label, 1, 0, "system_u:system_r:container_t:s0:c1,c2", "system_u:object_r:container_file_t:s0:c1,c2"),
        std::make_tuple(full_label, 4, 0, "fakeuser:fakerole:faketype:s0:c1,c2",  "fakeuser:object_r:container_file_t:s0:c1,c2"),
        std::make_tuple(nullptr, 0, 0, "system_u:system_r:container_t:s0", "system_u:object_r:container_file_t:s0"),
    };

    if (!is_selinux_enabled()) {
        SUCCEED() << "WARNING: The current machine does not support SELinux";
        return;
    }

    for (const auto &elem : normal) {
        char *process_label = nullptr;
        char *mount_label = nullptr;

        ASSERT_EQ(init_label(std::get<0>(elem), std::get<1>(elem), &process_label, &mount_label), std::get<2>(elem));
        if (!std::get<3>(elem).empty()) {
            std::string processLabel { process_label };
            processLabel.resize(std::get<3>(elem).size());
            ASSERT_STREQ(processLabel.c_str(), std::get<3>(elem).c_str());
            free(process_label);
        } else {
            ASSERT_STREQ(process_label, nullptr);
        }

        if (!std::get<4>(elem).empty()) {
            std::string mountLabel { mount_label };
            mountLabel.resize(std::get<4>(elem).size());
            ASSERT_STREQ(mountLabel.c_str(), std::get<4>(elem).c_str());
            free(mount_label);
        } else {
            ASSERT_STREQ(mount_label, nullptr);
        }
    }
}

TEST_F(SELinuxLabelUnitTest, test_init_label_abnormal)
{
    const char *invalid_key_label[] =  { "xxx" };
    const char *invalid_value_label[] =  { "user:" };

    std::vector<std::tuple<const char **, size_t, int, std::string, std::string>> normal {
        std::make_tuple(invalid_key_label, 1, -1, "", ""),
        std::make_tuple(invalid_value_label, 1, -1, "", ""),
    };

    if (!is_selinux_enabled()) {
        SUCCEED() << "WARNING: The current machine does not support SELinux";
        return;
    }

    for (const auto &elem : normal) {
        char *process_label = nullptr;
        char *mount_label = nullptr;

        ASSERT_EQ(init_label(std::get<0>(elem), std::get<1>(elem), &process_label, &mount_label), std::get<2>(elem));
        ASSERT_STREQ(process_label, nullptr);
        ASSERT_STREQ(mount_label, nullptr);
    }
}

TEST(SELinuxLabelUnitTestWithoutMock, test_dup_security_opt)
{
    const char *label = "system_u:object_r:container_file_t:s0";
    char **dst = nullptr;
    size_t len;

    ASSERT_EQ(dup_security_opt(label, &dst, &len), 0);
    ASSERT_EQ(len, 4);
    ASSERT_STREQ(dst[0], "user:system_u");
    ASSERT_STREQ(dst[1], "role:object_r");
    ASSERT_STREQ(dst[2], "type:container_file_t");
    ASSERT_STREQ(dst[3], "level:s0");
    util_free_array(dst);
    dst = nullptr;
    len = 0;

    ASSERT_EQ(dup_security_opt(nullptr, &dst, &len), 0);
    ASSERT_EQ(dst, nullptr);
}

class SELinuxRelabelUnitTest : public testing::Test {
protected:
    void SetUp() override
    {
        CreateTestedObjects();
    }

    void TearDown() override
    {
        ClearTestedObjects();
    }

private:
    void CreateTestedObjects()
    {
        struct stat st;

        if (lstat(m_testDir.c_str(), &st) < 0) {
            (void)mkdir(m_testDir.c_str(), S_IRUSR | S_IWUSR | S_IXUSR | S_IRWXG | S_IRWXO);
        }

        ofstream osm;
        osm.open(m_testFile);
        osm << "SELinux unit test";
        osm.close();
    }

    void ClearTestedObjects()
    {
        remove(m_testFile.c_str());
        rmdir(m_testDir.c_str());
    }

protected:
    std::string m_testDir { "./test_dir" };
    std::string m_testFile { m_testDir + "/file" };
};

TEST_F(SELinuxRelabelUnitTest, test_relabel_normal)
{
    std::vector<std::tuple<std::string, bool, int, std::string>> normal {
        std::make_tuple("system_u:object_r:container_file_t:s0:c100,c200", false, 0, "system_u:object_r:container_file_t:s0:c100,c200"),
        std::make_tuple("system_u:object_r:container_file_t:s0:c300,c300", false, 0, "system_u:object_r:container_file_t:s0:c300"),
        std::make_tuple("system_u:object_r:container_file_t:s0:c100,c200", true, 0, "system_u:object_r:container_file_t:s0"),
        std::make_tuple("system_u:object_r:container_file_t:s0:c300,c300", true, 0, "system_u:object_r:container_file_t:s0"),
    };

    if (!is_selinux_enabled()) {
        SUCCEED() << "WARNING: The current machine does not support SELinux";
        return;
    }

    for (const auto &elem : normal) {
        char *context = nullptr;

        ASSERT_EQ(relabel(m_testDir.c_str(), std::get<0>(elem).c_str(), std::get<1>(elem)), std::get<2>(elem));
        ASSERT_GE(lgetfilecon(m_testFile.c_str(), &context), 0);
        ASSERT_STREQ(context, std::get<3>(elem).c_str());
        freecon(context);
    }
}

TEST_F(SELinuxRelabelUnitTest, test_relabel_abnormal)
{
    std::vector<std::tuple<std::string, std::string, bool, int>> abnormal {
        // exclude path test
        std::make_tuple("/", "system_u:object_r:root_t:s0", true, -1),
        std::make_tuple("/usr", "system_u:object_r:usr_t:s0", true, -1),
        std::make_tuple("/etc", "system_u:object_r:etc_t:s0", true, -1),
        std::make_tuple("/tmp", "system_u:object_r:tmp_t:s0", true, -1),
        std::make_tuple("/home", "system_u:object_r:home_root_t:s0", true, -1),
        std::make_tuple("/run", "system_u:object_r:var_run_t:s0", true, -1),
        std::make_tuple("/var", "system_u:object_r:var_t:s0", true, -1),
        std::make_tuple("/root", "system_u:object_r:admin_home_t:s0", true, -1),
        // bad prefix test
        std::make_tuple("/usr/xxx", "system_u:object_r:usr_t:s0", true, -1),
    };

    if (!is_selinux_enabled()) {
        SUCCEED() << "WARNING: The current machine does not support SELinux";
        return;
    }

    for (const auto &elem : abnormal) {
        ASSERT_EQ(relabel(std::get<0>(elem).c_str(), std::get<1>(elem).c_str(), std::get<2>(elem)), std::get<3>(elem));
    }
}

TEST_F(SELinuxRelabelUnitTest, test_get_disable_security_opt)
{
    char **labels = nullptr;
    size_t labels_len;

    ASSERT_EQ(get_disable_security_opt(&labels, &labels_len), 0);
    ASSERT_EQ(labels_len, 1);
    ASSERT_NE(labels[0], "label=disable");

    util_free_array(labels);
}

