/*
 * cc-enterprise-login-dialog.c
 *
 * Copyright 2023 Red Hat Inc
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty 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, see <http://www.gnu.org/licenses/>.
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 *
 * Author(s):
 *   Felipe Borges <felipeborges@gnome.org>
 *   Ondrej Holy <oholy@redhat.com>
 */

#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "cc-enterprise-login-dialog"

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "cc-enterprise-login-dialog.h"
#include "cc-entry-feedback.h"
#include "cc-list-row.h"
#include "cc-realm-manager.h"
#include "user-utils.h"

#include <act/act.h>
#include <adwaita.h>
#include <config.h>
#include <errno.h>
#include <gio/gio.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include <locale.h>

#define DOMAIN_CHECK_TIMEOUT 600

struct _CcEnterpriseLoginDialog {
    AdwDialog parent_instance;

    AdwNavigationView *navigation;
    AdwNavigationPage *offline_page;
    AdwNavigationPage *main_page;
    AdwNavigationPage *main_preferences_page;
    AdwNavigationPage *enroll_page;
    AdwPreferencesPage *enroll_preferences_page;
    AdwToastOverlay *toast_overlay;

    GtkButton *add_button;
    AdwSpinner *main_page_spinner;
    AdwEntryRow *domain_row;
    CcEntryFeedback *domain_feedback;
    gint domain_timeout_id;
    AdwEntryRow *username_row;
    AdwPasswordEntryRow *password_row;

    GtkButton *enroll_button;
    AdwSpinner *enroll_page_spinner;
    AdwEntryRow *admin_name_row;
    AdwPasswordEntryRow *admin_password_row;

    CcRealmManager *realm_manager;
    CcRealmObject *selected_realm;
    GNetworkMonitor *network_monitor;

    GCancellable *cancellable;
};

G_DEFINE_FINAL_TYPE (CcEnterpriseLoginDialog, cc_enterprise_login_dialog, ADW_TYPE_DIALOG)

static gboolean
add_button_is_valid (CcEnterpriseLoginDialog *self)
{
    return (self->selected_realm != NULL) && strlen (gtk_editable_get_text (GTK_EDITABLE (self->username_row))) > 0
           && strlen (gtk_editable_get_text (GTK_EDITABLE (self->password_row))) > 0;
}

static gboolean
enroll_button_is_valid (CcEnterpriseLoginDialog *self)
{
    return strlen (gtk_editable_get_text (GTK_EDITABLE (self->admin_name_row))) > 0
           && strlen (gtk_editable_get_text (GTK_EDITABLE (self->admin_password_row))) > 0;
}

static void
show_operation_progress (CcEnterpriseLoginDialog *self, gboolean show)
{
    gtk_widget_set_visible (GTK_WIDGET (self->main_page_spinner), show);
    gtk_widget_set_visible (GTK_WIDGET (self->enroll_page_spinner), show);

    gtk_widget_set_sensitive (GTK_WIDGET (self->main_preferences_page), !show);
    gtk_widget_set_sensitive (GTK_WIDGET (self->add_button), !show && add_button_is_valid (self));
    gtk_widget_set_sensitive (GTK_WIDGET (self->enroll_preferences_page), !show);
    gtk_widget_set_sensitive (GTK_WIDGET (self->enroll_button), !show && enroll_button_is_valid (self));

    /* Hide passwords during operations. */
    if (show) {
        GtkEditable *delegate;

        delegate = gtk_editable_get_delegate (GTK_EDITABLE (self->password_row));
        gtk_text_set_visibility (GTK_TEXT (delegate), FALSE);

        delegate = gtk_editable_get_delegate (GTK_EDITABLE (self->admin_password_row));
        gtk_text_set_visibility (GTK_TEXT (delegate), FALSE);
    }
}

static void
cache_user_cb (GObject *source, GAsyncResult *result, gpointer user_data)
{
    g_autoptr(CcEnterpriseLoginDialog) self = CC_ENTERPRISE_LOGIN_DIALOG (user_data);
    g_autoptr(ActUser) user = NULL;
    g_autoptr(GError) error = NULL;

    user = act_user_manager_cache_user_finish (ACT_USER_MANAGER (source), result, &error);
    if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
        return;

    /* This is where we're finally done */
    if (user != NULL) {
        g_debug ("Successfully cached remote user: %s", act_user_get_user_name (user));

        adw_dialog_close (ADW_DIALOG (self));
    } else {
        g_message ("Couldn't cache user account: %s", error->message);

        adw_toast_overlay_add_toast (self->toast_overlay, adw_toast_new (_("Failed to register account")));
        show_operation_progress (self, FALSE);
    }
}

static void
change_login_policy_cb (GObject *source, GAsyncResult *result, gpointer user_data)
{
    g_autoptr(CcEnterpriseLoginDialog) self = CC_ENTERPRISE_LOGIN_DIALOG (user_data);
    g_autoptr(GError) error = NULL;

    cc_realm_common_call_change_login_policy_finish (CC_REALM_COMMON (source), result, &error);
    if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
        return;

    if (error == NULL) {
        ActUserManager *manager;
        g_autofree gchar *login = NULL;

        /*
         * Now tell the account service about this user. The account service
         * should also lookup information about this via the realm and make
         * sure all that is functional.
         */
        manager = act_user_manager_get_default ();
        login = cc_realm_calculate_login (CC_REALM_COMMON (source),
                                          gtk_editable_get_text (GTK_EDITABLE (self->username_row)));

        g_debug ("Caching remote user: %s", login);

        act_user_manager_cache_user_async (manager, login, self->cancellable, cache_user_cb, g_object_ref (self));
    } else {
        g_message ("Couldn't permit logins on account: %s", error->message);

        adw_toast_overlay_add_toast (self->toast_overlay, adw_toast_new (_("Failed to register account")));
        show_operation_progress (self, FALSE);
    }
}

static void
permit_user_login (CcEnterpriseLoginDialog *self)
{
    g_autoptr(CcRealmCommon) common = NULL;

    common = cc_realm_object_get_common (self->selected_realm);
    if (common == NULL) {
        g_debug ("Failed to register account: failed to get d-bus interface");

        adw_toast_overlay_add_toast (self->toast_overlay, adw_toast_new (_("Failed to register account")));
        show_operation_progress (self, FALSE);
    } else {
        g_autofree gchar *login = NULL;
        const gchar *add[2];
        const gchar *remove[1];
        GVariant *options;

        login = cc_realm_calculate_login (common, gtk_editable_get_text (GTK_EDITABLE (self->username_row)));

        g_debug ("Permitting login for: %s", login);

        add[0] = login;
        add[1] = NULL;
        remove[0] = NULL;

        options = g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0);

        cc_realm_common_call_change_login_policy (common, "", add, remove, options, self->cancellable,
                                                  change_login_policy_cb, g_object_ref (self));
    }
}

static void
realm_join_as_admin_cb (GObject *source, GAsyncResult *result, gpointer user_data)
{
    g_autoptr(CcEnterpriseLoginDialog) self = CC_ENTERPRISE_LOGIN_DIALOG (user_data);
    g_autoptr(GError) error = NULL;
    const gchar *message = NULL;

    cc_realm_join_finish (CC_REALM_OBJECT (source), result, &error);
    if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
        return;

    /* Yay, joined the domain, register the user locally */
    if (error == NULL) {
        g_debug ("Joining realm completed successfully");

        permit_user_login (self);
        return;
    }
    /* Other failure */
    else {
        g_message ("Failed to join the domain: %s", error->message);

        message = _("Failed to join domain");
    }

    adw_toast_overlay_add_toast (self->toast_overlay, adw_toast_new (message));
    show_operation_progress (self, FALSE);
}

static void
realm_login_as_admin_cb (GObject *source, GAsyncResult *result, gpointer user_data)
{
    g_autoptr(CcEnterpriseLoginDialog) self = CC_ENTERPRISE_LOGIN_DIALOG (user_data);
    g_autoptr(GError) error = NULL;
    g_autoptr(GBytes) creds = NULL;
    const gchar *message = NULL;

    creds = cc_realm_login_finish (result, &error);
    if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
        return;

    /* Logged in as admin successfully, use creds to join domain */
    if (creds != NULL) {
        if (cc_realm_join_as_admin (self->selected_realm, gtk_editable_get_text (GTK_EDITABLE (self->admin_name_row)),
                                    gtk_editable_get_text (GTK_EDITABLE (self->admin_password_row)), creds,
                                    self->cancellable, realm_join_as_admin_cb, g_object_ref (self))) {
            return;
        }

        g_message ("Authenticating as admin is not supported by the realm");

        message = _("No supported way to authenticate with this domain");
    }

    show_operation_progress (self, FALSE);

    /* A problem with the admin's login name or password */
    if (g_error_matches (error, CC_REALM_ERROR, CC_REALM_ERROR_BAD_LOGIN)) {
        g_debug ("Bad admin login: %s", error->message);

        message = _("That login name didn’t work");
        gtk_widget_grab_focus (GTK_WIDGET (self->admin_name_row));
    } else if (g_error_matches (error, CC_REALM_ERROR, CC_REALM_ERROR_BAD_PASSWORD)) {
        g_debug ("Bad admin password: %s", error->message);

        message = _("That login password didn’t work");
        gtk_widget_grab_focus (GTK_WIDGET (self->admin_password_row));
    } else if (g_error_matches (error, CC_REALM_ERROR, CC_REALM_ERROR_BAD_HOSTNAME)) {
        g_debug ("Bad host name: %s", error->message);

        message = _("That hostname didn’t work");
    }
    /* Other login failure */
    else {
        g_message ("Admin login failure: %s", error->message);

        message = _("Failed to log into domain");
    }

    adw_toast_overlay_add_toast (self->toast_overlay, adw_toast_new (message));
}

static void
on_enroll_button_clicked_cb (CcEnterpriseLoginDialog *self)
{
    g_debug ("Logging in as admin user: %s", gtk_editable_get_text (GTK_EDITABLE (self->admin_name_row)));

    show_operation_progress (self, TRUE);

    /* Prompted for some admin credentials, try to use them to log in */
    cc_realm_login (self->selected_realm, gtk_editable_get_text (GTK_EDITABLE (self->admin_name_row)),
                    gtk_editable_get_text (GTK_EDITABLE (self->admin_password_row)), self->cancellable,
                    realm_login_as_admin_cb, g_object_ref (self));
}

static void
enroll_page_validate (CcEnterpriseLoginDialog *self)
{
    gtk_widget_set_sensitive (GTK_WIDGET (self->enroll_button), enroll_button_is_valid (self));
}

static void
clear_enroll_page (CcEnterpriseLoginDialog *self)
{
    gtk_editable_set_text (GTK_EDITABLE (self->admin_name_row), "");
    gtk_editable_set_text (GTK_EDITABLE (self->admin_password_row), "");
}

static void
show_enroll_page (CcEnterpriseLoginDialog *self)
{
    const gchar *domain;
    g_autofree gchar *description = NULL;

    domain = gtk_editable_get_text (GTK_EDITABLE (self->domain_row));

    /* Translators: The "%s" is a domain address (e.g. "demo1.freeipa.org"). */
    description =
        g_strdup_printf (_("To add an enterprise login account, this device needs to be enrolled with <b>%s</b>. "
                                   "To enroll, have your domain administrator enter their name and password."),
                           domain);
    adw_preferences_page_set_description (self->enroll_preferences_page, description);

    if (strlen (gtk_editable_get_text (GTK_EDITABLE (self->admin_name_row))) == 0) {
        g_autoptr(CcRealmKerberosMembership) membership = NULL;
        g_autoptr(CcRealmKerberos) kerberos = NULL;
        const gchar *name;

        kerberos = cc_realm_object_get_kerberos (self->selected_realm);
        membership = cc_realm_object_get_kerberos_membership (self->selected_realm);
        name = cc_realm_kerberos_membership_get_suggested_administrator (membership);
        if (name != NULL && !g_str_equal (name, "")) {
            g_debug ("Suggesting admin user: %s", name);

            gtk_editable_set_text (GTK_EDITABLE (self->admin_name_row), name);
        }
    }

    adw_navigation_view_push (self->navigation, self->enroll_page);
}

static void
realm_join_as_user_cb (GObject *source, GAsyncResult *result, gpointer user_data)
{
    g_autoptr(CcEnterpriseLoginDialog) self = CC_ENTERPRISE_LOGIN_DIALOG (user_data);
    g_autoptr(GError) error = NULL;

    cc_realm_join_finish (CC_REALM_OBJECT (source), result, &error);
    if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
        return;

    /* Yay, joined the domain, register the user locally */
    if (error == NULL) {
        g_debug ("Joining realm completed successfully");

        permit_user_login (self);
        return;
    }
    /* Credential failure while joining domain, prompt for admin creds */
    else if (g_error_matches (error, CC_REALM_ERROR, CC_REALM_ERROR_BAD_LOGIN)
             || g_error_matches (error, CC_REALM_ERROR, CC_REALM_ERROR_BAD_PASSWORD)
             || g_error_matches (error, CC_REALM_ERROR, CC_REALM_ERROR_BAD_HOSTNAME)) {
        g_debug ("Joining realm failed due to credentials");

        show_enroll_page (self);
    }
    /* Other failure */
    else {
        g_message ("Failed to join the domain: %s", error->message);

        adw_toast_overlay_add_toast (self->toast_overlay, adw_toast_new (_("Failed to join domain")));
    }

    show_operation_progress (self, FALSE);
}

static void
realm_login_cb (GObject *source, GAsyncResult *result, gpointer user_data)
{
    g_autoptr(CcEnterpriseLoginDialog) self = CC_ENTERPRISE_LOGIN_DIALOG (user_data);
    g_autoptr(GError) error = NULL;
    g_autoptr(GBytes) creds = NULL;
    const gchar *message = NULL;

    creds = cc_realm_login_finish (result, &error);
    if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
        return;

    /*
     * User login is valid, but cannot authenticate right now (eg: user needs
     * to change password at next login etc.)
     */
    if (g_error_matches (error, CC_REALM_ERROR, CC_REALM_ERROR_CANNOT_AUTH))
        g_clear_error (&error);

    if (error == NULL) {
        /* Already joined to the domain, just register this user */
        if (cc_realm_is_configured (self->selected_realm)) {
            g_debug ("Already joined to this realm");

            permit_user_login (self);
        }
        /* Join the domain, try using the user's creds */
        else if (creds != NULL
                 && cc_realm_join_as_user (self->selected_realm,
                                           gtk_editable_get_text (GTK_EDITABLE (self->username_row)),
                                           gtk_editable_get_text (GTK_EDITABLE (self->password_row)), creds,
                                           self->cancellable, realm_join_as_user_cb, g_object_ref (self))) {
            return;
        }
        /* If we can't do user auth, try to authenticate as admin */
        else {
            g_debug ("Cannot join with user credentials");

            show_enroll_page (self);
            show_operation_progress (self, FALSE);
        }

        return;
    }

    show_operation_progress (self, FALSE);

    /* A problem with the user's login name or password */
    if (g_error_matches (error, CC_REALM_ERROR, CC_REALM_ERROR_BAD_LOGIN)) {
        g_debug ("Problem with the user's login: %s", error->message);

        message = _("That login name didn’t work");
        gtk_widget_grab_focus (GTK_WIDGET (self->username_row));
    } else if (g_error_matches (error, CC_REALM_ERROR, CC_REALM_ERROR_BAD_PASSWORD)) {
        g_debug ("Problem with the user's password: %s", error->message);

        message = _("That login password didn’t work");
        gtk_widget_grab_focus (GTK_WIDGET (self->password_row));
    }
    /* Other login failure */
    else {
        g_message ("Couldn't log in as user: %s", error->message);

        message = _("Failed to log into domain");
        gtk_widget_grab_focus (GTK_WIDGET (self->domain_row));
    }

    adw_toast_overlay_add_toast (self->toast_overlay, adw_toast_new (message));
}

static void
on_add_button_clicked_cb (CcEnterpriseLoginDialog *self)
{
    show_operation_progress (self, TRUE);

    cc_realm_login (self->selected_realm, gtk_editable_get_text (GTK_EDITABLE (self->username_row)),
                    gtk_editable_get_text (GTK_EDITABLE (self->password_row)), self->cancellable, realm_login_cb,
                    g_object_ref (self));
}

static void
main_page_validate (CcEnterpriseLoginDialog *self)
{
    gtk_widget_set_sensitive (GTK_WIDGET (self->add_button), add_button_is_valid (self));
}

static void
realm_manager_discover_cb (GObject *source, GAsyncResult *result, gpointer user_data)
{
    g_autoptr(CcEnterpriseLoginDialog) self = CC_ENTERPRISE_LOGIN_DIALOG (user_data);
    g_autoptr(GError) error = NULL;
    GList *realms;

    realms = cc_realm_manager_discover_finish (self->realm_manager, result, NULL);
    if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
        return;

    /* Found a realm, log user into domain */
    if (realms != NULL) {
        self->selected_realm = g_object_ref (realms->data);

        cc_entry_feedback_update (self->domain_feedback, "check-outlined-symbolic", _("Valid domain"));
        gtk_widget_remove_css_class (GTK_WIDGET (self->domain_row), "error");
    } else {
        cc_entry_feedback_update (self->domain_feedback, "dialog-error-symbolic", _("Domain not found"));
        gtk_widget_add_css_class (GTK_WIDGET (self->domain_row), "error");
    }

    main_page_validate (self);

    g_list_free_full (realms, g_object_unref);
}

static gboolean
domain_validate (void *user_data)
{
    CcEnterpriseLoginDialog *self = CC_ENTERPRISE_LOGIN_DIALOG (user_data);

    self->domain_timeout_id = 0;

    /* This is needed to stop previous calls to avoid rewriting feedback. */
    g_cancellable_cancel (self->cancellable);
    g_object_unref (self->cancellable);
    self->cancellable = g_cancellable_new ();

    cc_realm_manager_discover (self->realm_manager, gtk_editable_get_text (GTK_EDITABLE (self->domain_row)),
                               self->cancellable, realm_manager_discover_cb, g_object_ref (self));

    return G_SOURCE_REMOVE;
}

static void
on_domain_entry_changed_cb (CcEnterpriseLoginDialog *self)
{
    const gchar *domain;

    if (self->realm_manager == NULL)
        return;

    g_clear_handle_id (&self->domain_timeout_id, g_source_remove);
    clear_enroll_page (self);

    domain = gtk_editable_get_text (GTK_EDITABLE (self->domain_row));
    if (strlen (domain) == 0) {
        cc_entry_feedback_reset (self->domain_feedback);
        return;
    }

    cc_entry_feedback_update (self->domain_feedback, CC_ENTRY_LOADING, _("Checking domain…"));
    self->domain_timeout_id = g_timeout_add (DOMAIN_CHECK_TIMEOUT, domain_validate, self);
}

static void
check_network_availability (CcEnterpriseLoginDialog *self)
{
    if (!g_network_monitor_get_network_available (self->network_monitor)) {
        adw_navigation_view_pop_to_page (self->navigation, self->offline_page);
    } else {
        if (adw_navigation_view_get_visible_page (self->navigation) != self->main_page)
            adw_navigation_view_push (self->navigation, self->main_page);
    }
}

static void
on_network_changed_cb (GNetworkMonitor *monitor, gboolean available, gpointer user_data)
{
    CcEnterpriseLoginDialog *self = CC_ENTERPRISE_LOGIN_DIALOG (user_data);

    check_network_availability (self);
}

static void
realm_manager_new_cb (GObject *source, GAsyncResult *result, gpointer user_data)
{
    g_autoptr(CcEnterpriseLoginDialog) self = CC_ENTERPRISE_LOGIN_DIALOG (user_data);
    g_autoptr(GError) error = NULL;

    self->realm_manager = cc_realm_manager_new_finish (result, &error);
    if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
        return;

    if (error != NULL) {
        g_warning ("Couldn't contact realmd service: %s", error->message);

        adw_toast_overlay_add_toast (self->toast_overlay, adw_toast_new (_("Failed to contact realmd service")));
    } else {
        show_operation_progress (self, FALSE);
    }
}

static void
cc_enterprise_login_dialog_dispose (GObject *object)
{
    CcEnterpriseLoginDialog *self = CC_ENTERPRISE_LOGIN_DIALOG (object);

    if (self->cancellable)
        g_cancellable_cancel (self->cancellable);
    g_clear_object (&self->cancellable);

    g_clear_object (&self->realm_manager);

    g_signal_handlers_disconnect_by_data (self->network_monitor, self);

    g_clear_handle_id (&self->domain_timeout_id, g_source_remove);

    G_OBJECT_CLASS (cc_enterprise_login_dialog_parent_class)->dispose (object);
}

static void
cc_enterprise_login_dialog_init (CcEnterpriseLoginDialog *self)
{
    gtk_widget_init_template (GTK_WIDGET (self));

    self->cancellable = g_cancellable_new ();

    self->network_monitor = g_network_monitor_get_default ();
    g_signal_connect_object (self->network_monitor, "network-changed", G_CALLBACK (on_network_changed_cb), self, 0);
    check_network_availability (self);

    show_operation_progress (self, TRUE);
    cc_realm_manager_new (self->cancellable, realm_manager_new_cb, g_object_ref (self));
}

static void
cc_enterprise_login_dialog_class_init (CcEnterpriseLoginDialogClass *klass)
{
    GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
    GObjectClass *object_class = G_OBJECT_CLASS (klass);

    object_class->dispose = cc_enterprise_login_dialog_dispose;

    g_type_ensure (CC_TYPE_ENTRY_FEEDBACK);
    g_type_ensure (CC_TYPE_LIST_ROW);

    gtk_widget_class_set_template_from_resource (
        widget_class, "/org/gnome/control-center/system/users/cc-enterprise-login-dialog.ui");

    gtk_widget_class_bind_template_child (widget_class, CcEnterpriseLoginDialog, add_button);
    gtk_widget_class_bind_template_child (widget_class, CcEnterpriseLoginDialog, admin_name_row);
    gtk_widget_class_bind_template_child (widget_class, CcEnterpriseLoginDialog, admin_password_row);
    gtk_widget_class_bind_template_child (widget_class, CcEnterpriseLoginDialog, domain_feedback);
    gtk_widget_class_bind_template_child (widget_class, CcEnterpriseLoginDialog, domain_row);
    gtk_widget_class_bind_template_child (widget_class, CcEnterpriseLoginDialog, enroll_button);
    gtk_widget_class_bind_template_child (widget_class, CcEnterpriseLoginDialog, enroll_page);
    gtk_widget_class_bind_template_child (widget_class, CcEnterpriseLoginDialog, enroll_page_spinner);
    gtk_widget_class_bind_template_child (widget_class, CcEnterpriseLoginDialog, enroll_preferences_page);
    gtk_widget_class_bind_template_child (widget_class, CcEnterpriseLoginDialog, main_page);
    gtk_widget_class_bind_template_child (widget_class, CcEnterpriseLoginDialog, main_page_spinner);
    gtk_widget_class_bind_template_child (widget_class, CcEnterpriseLoginDialog, main_preferences_page);
    gtk_widget_class_bind_template_child (widget_class, CcEnterpriseLoginDialog, navigation);
    gtk_widget_class_bind_template_child (widget_class, CcEnterpriseLoginDialog, offline_page);
    gtk_widget_class_bind_template_child (widget_class, CcEnterpriseLoginDialog, password_row);
    gtk_widget_class_bind_template_child (widget_class, CcEnterpriseLoginDialog, toast_overlay);
    gtk_widget_class_bind_template_child (widget_class, CcEnterpriseLoginDialog, username_row);

    gtk_widget_class_bind_template_callback (widget_class, enroll_page_validate);
    gtk_widget_class_bind_template_callback (widget_class, main_page_validate);
    gtk_widget_class_bind_template_callback (widget_class, on_add_button_clicked_cb);
    gtk_widget_class_bind_template_callback (widget_class, on_domain_entry_changed_cb);
    gtk_widget_class_bind_template_callback (widget_class, on_enroll_button_clicked_cb);
}

CcEnterpriseLoginDialog *
cc_enterprise_login_dialog_new (void)
{
    return g_object_new (CC_TYPE_ENTERPRISE_LOGIN_DIALOG, NULL);
}
