/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
 * Copyright (C) 2010 - 2014 Red Hat, Inc.
 */

#include "libnm-client-impl/nm-default-libnm.h"

#include <sys/types.h>
#include <signal.h>

#include "nm-secret-agent-old.h"

#include "libnm-client-test/nm-test-libnm-utils.h"

/*****************************************************************************/

enum {
    SECRET_REQUESTED,
    LAST_SIGNAL,
};

static guint signals[LAST_SIGNAL] = {0};

typedef NMSecretAgentOld      TestSecretAgent;
typedef NMSecretAgentOldClass TestSecretAgentClass;

GType test_secret_agent_get_type(void);

G_DEFINE_TYPE(TestSecretAgent, test_secret_agent, NM_TYPE_SECRET_AGENT_OLD)

static void
test_secret_agent_get_secrets(NMSecretAgentOld              *agent,
                              NMConnection                  *connection,
                              const char                    *connection_path,
                              const char                    *setting_name,
                              const char                   **hints,
                              NMSecretAgentGetSecretsFlags   flags,
                              NMSecretAgentOldGetSecretsFunc callback,
                              gpointer                       callback_data)
{
    NMSettingWirelessSecurity *s_wsec;
    GVariant                  *secrets = NULL;
    GVariantBuilder            secrets_builder, setting_builder;
    char                      *secret = NULL;
    GError                    *error  = NULL;

    g_assert_cmpstr(setting_name, ==, NM_SETTING_WIRELESS_SECURITY_SETTING_NAME);

    s_wsec = nm_connection_get_setting_wireless_security(connection);
    g_assert(s_wsec);
    g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "wpa-psk");
    g_assert_cmpstr(nm_setting_wireless_security_get_psk(s_wsec), ==, NULL);

    g_signal_emit(agent,
                  signals[SECRET_REQUESTED],
                  0,
                  connection,
                  connection_path,
                  NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
                  NM_SETTING_WIRELESS_SECURITY_PSK,
                  &secret);

    if (!secret) {
        error = g_error_new(NM_SECRET_AGENT_ERROR, NM_SECRET_AGENT_ERROR_NO_SECRETS, "No secrets");
        goto done;
    }

    if (!strcmp(secret, "CANCEL")) {
        error = g_error_new(NM_SECRET_AGENT_ERROR,
                            NM_SECRET_AGENT_ERROR_USER_CANCELED,
                            "User canceled");
        goto done;
    }

    g_variant_builder_init(&setting_builder, NM_VARIANT_TYPE_SETTING);
    g_variant_builder_add(&setting_builder,
                          "{sv}",
                          NM_SETTING_WIRELESS_SECURITY_PSK,
                          g_variant_new_string(secret));

    g_variant_builder_init(&secrets_builder, NM_VARIANT_TYPE_CONNECTION);
    g_variant_builder_add(&secrets_builder, "{sa{sv}}", setting_name, &setting_builder);
    secrets = g_variant_ref_sink(g_variant_builder_end(&secrets_builder));

done:
    callback(agent, connection, secrets, error, callback_data);
    g_clear_error(&error);
    nm_clear_pointer(&secrets, g_variant_unref);
    g_free(secret);
}

static void
test_secret_agent_cancel_get_secrets(NMSecretAgentOld *agent,
                                     const char       *connection_path,
                                     const char       *setting_name)
{
    g_assert_not_reached();
}

static void
test_secret_agent_save_secrets(NMSecretAgentOld               *agent,
                               NMConnection                   *connection,
                               const char                     *connection_path,
                               NMSecretAgentOldSaveSecretsFunc callback,
                               gpointer                        callback_data)
{
    g_assert_not_reached();
}

static void
test_secret_agent_delete_secrets(NMSecretAgentOld                 *agent,
                                 NMConnection                     *connection,
                                 const char                       *connection_path,
                                 NMSecretAgentOldDeleteSecretsFunc callback,
                                 gpointer                          callback_data)
{
    g_assert_not_reached();
}

static void
test_secret_agent_init(TestSecretAgent *agent)
{}

static NMSecretAgentOld *
test_secret_agent_new(gboolean auto_register)
{
    return nmtstc_context_object_new(test_secret_agent_get_type(),
                                     TRUE,
                                     NM_SECRET_AGENT_OLD_IDENTIFIER,
                                     "test-secret-agent",
                                     NM_SECRET_AGENT_OLD_AUTO_REGISTER,
                                     auto_register,
                                     NULL);
}

static void
test_secret_agent_class_init(TestSecretAgentClass *klass)
{
    GObjectClass          *object_class = G_OBJECT_CLASS(klass);
    NMSecretAgentOldClass *agent_class  = NM_SECRET_AGENT_OLD_CLASS(klass);

    agent_class->get_secrets        = test_secret_agent_get_secrets;
    agent_class->cancel_get_secrets = test_secret_agent_cancel_get_secrets;
    agent_class->save_secrets       = test_secret_agent_save_secrets;
    agent_class->delete_secrets     = test_secret_agent_delete_secrets;

    signals[SECRET_REQUESTED] = g_signal_new("secret-requested",
                                             G_OBJECT_CLASS_TYPE(object_class),
                                             G_SIGNAL_RUN_LAST,
                                             0,
                                             NULL,
                                             NULL,
                                             NULL,
                                             G_TYPE_STRING,
                                             4,
                                             NM_TYPE_CONNECTION,
                                             G_TYPE_STRING,
                                             G_TYPE_STRING,
                                             G_TYPE_STRING);
}

/*****************************************************************************/

typedef struct {
    NMTstcServiceInfo *sinfo;
    NMClient          *client;

    NMSecretAgentOld *agent;
    NMDevice         *device;
    NMConnection     *connection;

    GMainLoop *loop;
    GSource   *timeout_source;

    char *ifname;
    char *con_id;

    int secrets_requested;
} TestSecretAgentData;

static void
connection_added_cb(GObject *s, GAsyncResult *result, gpointer user_data)
{
    TestSecretAgentData *sadata = user_data;
    NMRemoteConnection  *connection;
    GError              *error = NULL;

    connection = nm_client_add_connection_finish(sadata->client, result, &error);

    g_assert_no_error(error);
    g_assert_cmpstr(nm_connection_get_id(NM_CONNECTION(connection)), ==, sadata->con_id);

    sadata->connection = NM_CONNECTION(connection);
    g_main_loop_quit(sadata->loop);
}

static void
register_cb(GObject *object, GAsyncResult *result, gpointer user_data)
{
    TestSecretAgentData *sadata = user_data;
    GError              *error  = NULL;

    nm_secret_agent_old_register_finish(sadata->agent, result, &error);
    g_assert_no_error(error);
    g_assert(nm_secret_agent_old_get_registered(sadata->agent));

    g_main_loop_quit(sadata->loop);
}

#define TEST_CON_ID_PREFIX "test-secret-agent"

static void
test_setup(TestSecretAgentData *sadata, gconstpointer test_data)
{
    static int            static_counter = 0;
    const int             counter        = static_counter++;
    const char           *create_agent   = test_data;
    NMConnection         *connection;
    NMSettingConnection  *s_con;
    NMSettingWireless    *s_wireless;
    GBytes               *ssid;
    NMSetting            *s_wsec;
    gs_free_error GError *error = NULL;

    sadata->sinfo = nmtstc_service_init();
    if (!sadata->sinfo)
        return;

    g_assert(nm_g_main_context_is_thread_default(NULL));

    sadata->client = nmtstc_client_new(TRUE);

    g_assert(nm_g_main_context_is_thread_default(NULL));
    g_assert(nm_g_main_context_is_thread_default(nm_client_get_main_context(sadata->client)));

    sadata->loop = g_main_loop_new(NULL, FALSE);

    sadata->timeout_source = g_timeout_source_new_seconds(5);
    g_source_set_callback(sadata->timeout_source, nmtst_g_source_assert_not_called, NULL, NULL);
    g_source_attach(sadata->timeout_source, NULL);

    sadata->ifname = g_strdup_printf("wlan%d", counter);
    sadata->con_id = g_strdup_printf("%s-%d", TEST_CON_ID_PREFIX, counter);

    sadata->device =
        nmtstc_service_add_device(sadata->sinfo, sadata->client, "AddWifiDevice", sadata->ifname);

    /* Create the connection */
    connection = nmtst_create_minimal_connection(sadata->con_id,
                                                 NULL,
                                                 NM_SETTING_WIRELESS_SETTING_NAME,
                                                 &s_con);
    g_object_set(s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, sadata->ifname, NULL);

    s_wireless = nm_connection_get_setting_wireless(connection);
    ssid       = g_bytes_new("foo", 3);
    g_object_set(s_wireless, NM_SETTING_WIRELESS_SSID, ssid, NULL);
    g_bytes_unref(ssid);

    s_wsec = g_object_new(NM_TYPE_SETTING_WIRELESS_SECURITY,
                          NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
                          "wpa-psk",
                          NULL);
    nm_connection_add_setting(connection, s_wsec);

    nm_client_add_connection_async(sadata->client,
                                   connection,
                                   TRUE,
                                   NULL,
                                   connection_added_cb,
                                   sadata);
    g_object_unref(connection);

    g_main_loop_run(sadata->loop);
    g_assert(sadata->connection);

    if (nm_streq(create_agent, "1")) {
        gboolean auto_register = nmtst_get_rand_bool();

        sadata->agent = test_secret_agent_new(auto_register);

        if (auto_register) {
            g_assert(nm_secret_agent_old_get_registered(sadata->agent));
            nm_secret_agent_old_register(sadata->agent, NULL, &error);
            g_assert_no_error(error);
        } else {
            g_assert(!nm_secret_agent_old_get_registered(sadata->agent));
            nm_secret_agent_old_register_async(sadata->agent, NULL, register_cb, sadata);
            g_main_loop_run(sadata->loop);
        }

        g_assert(nm_secret_agent_old_get_registered(sadata->agent));
    }
}

static void
test_cleanup(TestSecretAgentData *sadata, gconstpointer test_data)
{
    GVariant                   *ret;
    GError                     *error        = NULL;
    NMTstContextBusyWatcherData watcher_data = {};

    g_assert(nm_g_main_context_is_thread_default(NULL));

    if (!sadata->sinfo)
        return;

    g_assert(nm_g_main_context_is_thread_default(nm_client_get_main_context(sadata->client)));

    nmtst_context_busy_watcher_add(&watcher_data,
                                   nm_client_get_context_busy_watcher(sadata->client));

    if (sadata->agent) {
        nmtst_context_busy_watcher_add(&watcher_data,
                                       nm_secret_agent_old_get_context_busy_watcher(sadata->agent));

        if (nm_secret_agent_old_get_registered(sadata->agent)) {
            nm_secret_agent_old_unregister(sadata->agent, NULL, &error);
            g_assert_no_error(error);
        }
        g_object_unref(sadata->agent);
    }

    ret =
        g_dbus_proxy_call_sync(sadata->sinfo->proxy,
                               "RemoveDevice",
                               g_variant_new("(s)", nm_object_get_path(NM_OBJECT(sadata->device))),
                               G_DBUS_CALL_FLAGS_NO_AUTO_START,
                               3000,
                               NULL,
                               &error);
    g_assert_no_error(error);
    g_variant_unref(ret);

    g_object_unref(sadata->connection);
    g_object_unref(sadata->client);

    nmtstc_service_cleanup(sadata->sinfo);

    nm_clear_g_source_inst(&sadata->timeout_source);

    g_main_loop_unref(sadata->loop);

    g_free(sadata->ifname);
    g_free(sadata->con_id);

    *sadata = (TestSecretAgentData) {};

    nmtst_context_busy_watcher_wait(&watcher_data);

    while (g_main_context_iteration(NULL, FALSE)) {}

    nmtst_main_context_assert_no_dispatch(NULL, nmtst_get_rand_uint32() % 500);
}

/*****************************************************************************/

static void
connection_activated_none_cb(GObject *c, GAsyncResult *result, gpointer user_data)
{
    TestSecretAgentData  *sadata = user_data;
    gs_free_error GError *error  = NULL;

    nm_client_activate_connection_finish(sadata->client, result, &error);
    g_assert_error(error, NM_AGENT_MANAGER_ERROR, NM_AGENT_MANAGER_ERROR_NO_SECRETS);

    g_main_loop_quit(sadata->loop);
}

static void
test_secret_agent_none(TestSecretAgentData *sadata, gconstpointer test_data)
{
    if (!nmtstc_service_available(sadata->sinfo))
        return;

    nm_client_activate_connection_async(sadata->client,
                                        sadata->connection,
                                        sadata->device,
                                        NULL,
                                        NULL,
                                        connection_activated_none_cb,
                                        sadata);
    g_main_loop_run(sadata->loop);
}

/*****************************************************************************/

static char *
secrets_requested_no_secrets_cb(TestSecretAgent *agent,
                                NMConnection    *connection,
                                const char      *connection_path,
                                const char      *setting_name,
                                const char      *secret_name,
                                gpointer         user_data)
{
    TestSecretAgentData *sadata = user_data;

    g_assert_cmpstr(connection_path, ==, nm_connection_get_path(sadata->connection));
    sadata->secrets_requested++;

    return NULL;
}

static void
connection_activated_no_secrets_cb(GObject *c, GAsyncResult *result, gpointer user_data)
{
    TestSecretAgentData                *sadata = user_data;
    gs_unref_object NMActiveConnection *ac     = NULL;
    gs_free_error GError               *error  = NULL;

    ac = nm_client_activate_connection_finish(sadata->client, result, &error);
    g_assert_error(error, NM_AGENT_MANAGER_ERROR, NM_AGENT_MANAGER_ERROR_NO_SECRETS);
    g_main_loop_quit(sadata->loop);
}

static void
test_secret_agent_no_secrets(TestSecretAgentData *sadata, gconstpointer test_data)
{
    if (!nmtstc_service_available(sadata->sinfo))
        return;

    g_signal_connect(sadata->agent,
                     "secret-requested",
                     G_CALLBACK(secrets_requested_no_secrets_cb),
                     sadata);

    nm_client_activate_connection_async(sadata->client,
                                        sadata->connection,
                                        sadata->device,
                                        NULL,
                                        NULL,
                                        connection_activated_no_secrets_cb,
                                        sadata);
    g_main_loop_run(sadata->loop);

    g_assert_cmpint(sadata->secrets_requested, ==, 1);
}

/*****************************************************************************/

static void
connection_activated_cancel_cb(GObject *c, GAsyncResult *result, gpointer user_data)
{
    TestSecretAgentData                *sadata = user_data;
    gs_unref_object NMActiveConnection *ac     = NULL;
    gs_free_error GError               *error  = NULL;

    ac = nm_client_activate_connection_finish(sadata->client, result, &error);
    g_assert_error(error, NM_AGENT_MANAGER_ERROR, NM_AGENT_MANAGER_ERROR_USER_CANCELED);
    g_main_loop_quit(sadata->loop);
}

static char *
secrets_requested_cancel_cb(TestSecretAgent *agent,
                            NMConnection    *connection,
                            const char      *connection_path,
                            const char      *setting_name,
                            const char      *secret_name,
                            gpointer         user_data)
{
    TestSecretAgentData *sadata = user_data;

    g_assert_cmpstr(connection_path, ==, nm_connection_get_path(sadata->connection));
    sadata->secrets_requested++;

    return g_strdup("CANCEL");
}

static void
test_secret_agent_cancel(TestSecretAgentData *sadata, gconstpointer test_data)
{
    if (!nmtstc_service_available(sadata->sinfo))
        return;

    g_signal_connect(sadata->agent,
                     "secret-requested",
                     G_CALLBACK(secrets_requested_cancel_cb),
                     sadata);

    nm_client_activate_connection_async(sadata->client,
                                        sadata->connection,
                                        sadata->device,
                                        NULL,
                                        NULL,
                                        connection_activated_cancel_cb,
                                        sadata);
    g_main_loop_run(sadata->loop);

    g_assert_cmpint(sadata->secrets_requested, ==, 1);
}

/*****************************************************************************/

static void
connection_activated_good_cb(GObject *c, GAsyncResult *result, gpointer user_data)
{
    TestSecretAgentData *sadata = user_data;
    NMActiveConnection  *ac;
    GError              *error = NULL;

    ac = nm_client_activate_connection_finish(sadata->client, result, &error);
    g_assert_no_error(error);

    g_object_unref(ac);

    g_main_loop_quit(sadata->loop);
}

static char *
secrets_requested_good_cb(TestSecretAgent *agent,
                          NMConnection    *connection,
                          const char      *connection_path,
                          const char      *setting_name,
                          const char      *secret_name,
                          gpointer         user_data)
{
    TestSecretAgentData *sadata = user_data;

    g_assert_cmpstr(connection_path, ==, nm_connection_get_path(sadata->connection));
    sadata->secrets_requested++;

    return g_strdup("password");
}

static void
test_secret_agent_good(TestSecretAgentData *sadata, gconstpointer test_data)
{
    if (!nmtstc_service_available(sadata->sinfo))
        return;

    g_signal_connect(sadata->agent,
                     "secret-requested",
                     G_CALLBACK(secrets_requested_good_cb),
                     sadata);

    nm_client_activate_connection_async(sadata->client,
                                        sadata->connection,
                                        sadata->device,
                                        NULL,
                                        NULL,
                                        connection_activated_good_cb,
                                        sadata);
    g_main_loop_run(sadata->loop);

    g_assert_cmpint(sadata->secrets_requested, ==, 1);
}

/*****************************************************************************/

static void
async_init_cb(GObject *object, GAsyncResult *result, gpointer user_data)
{
    GMainLoop               *loop  = user_data;
    gs_free_error GError    *error = NULL;
    gs_unref_object GObject *agent = NULL;

    agent = g_async_initable_new_finish(G_ASYNC_INITABLE(object), result, &error);
    nmtst_assert_success(NM_IS_SECRET_AGENT_OLD(agent), error);
    g_assert(!nm_secret_agent_old_get_registered(NM_SECRET_AGENT_OLD(agent)));

    g_main_loop_quit(loop);
}

static void
test_secret_agent_nm_not_running(void)
{
    gs_unref_object NMSecretAgentOld  *agent = NULL;
    nm_auto_unref_gmainloop GMainLoop *loop  = NULL;
    GError                            *error = NULL;

    agent = g_initable_new(test_secret_agent_get_type(),
                           NULL,
                           &error,
                           NM_SECRET_AGENT_OLD_IDENTIFIER,
                           "test-secret-agent",
                           NULL);
    nmtst_assert_success(NM_IS_SECRET_AGENT_OLD(agent), error);
    g_assert(!nm_secret_agent_old_get_registered(agent));
    g_clear_object(&agent);

    loop = g_main_loop_new(NULL, FALSE);
    g_async_initable_new_async(test_secret_agent_get_type(),
                               G_PRIORITY_DEFAULT,
                               NULL,
                               async_init_cb,
                               loop,
                               NM_SECRET_AGENT_OLD_IDENTIFIER,
                               "test-secret-agent",
                               NULL);
    g_main_loop_run(loop);
}

/*****************************************************************************/

typedef struct {
    int step;
    int invoke_count;
} AutoRegisterData;

static void
registered_changed(GObject *object, GParamSpec *pspec, gpointer user_data)
{
    NMSecretAgentOld *agent = NM_SECRET_AGENT_OLD(object);
    AutoRegisterData *data  = user_data;

    g_assert(data);
    g_assert(NM_IS_SECRET_AGENT_OLD(agent));

    data->invoke_count++;
    g_assert_cmpint(data->invoke_count, ==, data->step);

    switch (data->step) {
    case 1:
    case 3:
        g_assert(nm_secret_agent_old_get_registered(agent));
        break;
    case 2:
    case 4:
        g_assert(!nm_secret_agent_old_get_registered(agent));
        break;
    default:
        g_assert_not_reached();
    }
}

static void
test_secret_agent_auto_register(void)
{
    NMTstcServiceInfo                *sinfo;
    gs_unref_object NMSecretAgentOld *agent              = NULL;
    GError                           *error              = NULL;
    AutoRegisterData                  auto_register_data = {
                         .step         = 0,
                         .invoke_count = 0,
    };
    gulong                      signal_id;
    NMTstContextBusyWatcherData watcher_data = {};

    sinfo = nmtstc_service_init();
    if (!nmtstc_service_available(sinfo))
        return;

    agent = test_secret_agent_new(FALSE);
    g_assert(!nm_secret_agent_old_get_registered(agent));

    signal_id = g_signal_connect(agent,
                                 "notify::" NM_SECRET_AGENT_OLD_REGISTERED,
                                 G_CALLBACK(registered_changed),
                                 &auto_register_data);

    if (nmtst_get_rand_bool()) {
        g_object_set(agent, NM_SECRET_AGENT_OLD_AUTO_REGISTER, TRUE, NULL);
    } else
        nm_secret_agent_old_enable(agent, TRUE);
    g_assert(!nm_secret_agent_old_get_registered(agent));

    nm_secret_agent_old_register(agent, NULL, &error);
    g_assert_no_error(error);
    g_assert(!nm_secret_agent_old_get_registered(agent));

    auto_register_data.step = 1;
    nmtst_main_context_iterate_until_assert(NULL, 1000, nm_secret_agent_old_get_registered(agent));

    auto_register_data.step = 2;
    nm_secret_agent_old_enable(agent, FALSE);
    g_assert(!nm_secret_agent_old_get_registered(agent));

    nmtst_main_context_iterate_until(NULL, nmtst_get_rand_uint32() % 200, FALSE);

    g_assert(!nm_secret_agent_old_get_registered(agent));

    nmtstc_service_cleanup(sinfo);

    g_assert(!nm_secret_agent_old_get_registered(agent));

    nm_secret_agent_old_enable(agent, TRUE);

    g_assert(!nm_secret_agent_old_get_registered(agent));

    nmtst_main_context_iterate_until(NULL, nmtst_get_rand_uint32() % 200, FALSE);

    g_assert(!nm_secret_agent_old_get_registered(agent));

    sinfo = nmtstc_service_init();
    g_assert(nmtstc_service_available(sinfo));

    auto_register_data.step = 3;
    nmtst_main_context_iterate_until_assert(NULL, 1000, nm_secret_agent_old_get_registered(agent));

    nmtstc_service_cleanup(sinfo);

    auto_register_data.step = 4;
    nmtst_main_context_iterate_until_assert(NULL, 1000, !nm_secret_agent_old_get_registered(agent));

    nm_clear_g_signal_handler(agent, &signal_id);

    nmtst_context_busy_watcher_add(&watcher_data,
                                   nm_secret_agent_old_get_context_busy_watcher(agent));

    g_clear_object(&agent);

    nmtst_context_busy_watcher_wait(&watcher_data);

    nmtst_main_context_assert_no_dispatch(NULL, nmtst_get_rand_uint32() % 500);
}

/*****************************************************************************/

NMTST_DEFINE();

int
main(int argc, char **argv)
{
    g_setenv("LIBNM_USE_SESSION_BUS", "1", TRUE);

    nmtst_init(&argc, &argv, TRUE);

    g_test_add("/libnm/secret-agent/none",
               TestSecretAgentData,
               "0",
               test_setup,
               test_secret_agent_none,
               test_cleanup);
    g_test_add("/libnm/secret-agent/no-secrets",
               TestSecretAgentData,
               "1",
               test_setup,
               test_secret_agent_no_secrets,
               test_cleanup);
    g_test_add("/libnm/secret-agent/cancel",
               TestSecretAgentData,
               "1",
               test_setup,
               test_secret_agent_cancel,
               test_cleanup);
    g_test_add("/libnm/secret-agent/good",
               TestSecretAgentData,
               "1",
               test_setup,
               test_secret_agent_good,
               test_cleanup);
    g_test_add_func("/libnm/secret-agent/nm-not-running", test_secret_agent_nm_not_running);
    g_test_add_func("/libnm/secret-agent/auto-register", test_secret_agent_auto_register);

    return g_test_run();
}
