summaryrefslogtreecommitdiffstats
path: root/gnome-initial-setup/pages/account
diff options
context:
space:
mode:
Diffstat (limited to 'gnome-initial-setup/pages/account')
-rw-r--r--gnome-initial-setup/pages/account/account.gresource.xml9
-rw-r--r--gnome-initial-setup/pages/account/gis-account-avatar-chooser.ui51
-rw-r--r--gnome-initial-setup/pages/account/gis-account-page-enterprise.c863
-rw-r--r--gnome-initial-setup/pages/account/gis-account-page-enterprise.h42
-rw-r--r--gnome-initial-setup/pages/account/gis-account-page-enterprise.ui280
-rw-r--r--gnome-initial-setup/pages/account/gis-account-page-local.c721
-rw-r--r--gnome-initial-setup/pages/account/gis-account-page-local.h39
-rw-r--r--gnome-initial-setup/pages/account/gis-account-page-local.ui145
-rw-r--r--gnome-initial-setup/pages/account/gis-account-page.c309
-rw-r--r--gnome-initial-setup/pages/account/gis-account-page.h33
-rw-r--r--gnome-initial-setup/pages/account/gis-account-page.ui56
-rw-r--r--gnome-initial-setup/pages/account/gis-account-pages.c32
-rw-r--r--gnome-initial-setup/pages/account/gis-account-pages.h33
-rw-r--r--gnome-initial-setup/pages/account/meson.build33
-rw-r--r--gnome-initial-setup/pages/account/org.freedesktop.realmd.xml666
-rw-r--r--gnome-initial-setup/pages/account/um-photo-dialog.c314
-rw-r--r--gnome-initial-setup/pages/account/um-photo-dialog.h48
-rw-r--r--gnome-initial-setup/pages/account/um-realm-manager.c878
-rw-r--r--gnome-initial-setup/pages/account/um-realm-manager.h108
-rw-r--r--gnome-initial-setup/pages/account/um-utils.c568
-rw-r--r--gnome-initial-setup/pages/account/um-utils.h50
21 files changed, 5278 insertions, 0 deletions
diff --git a/gnome-initial-setup/pages/account/account.gresource.xml b/gnome-initial-setup/pages/account/account.gresource.xml
new file mode 100644
index 0000000..d698ba9
--- /dev/null
+++ b/gnome-initial-setup/pages/account/account.gresource.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/org/gnome/initial-setup">
+ <file preprocess="xml-stripblanks" alias="gis-account-avatar-chooser.ui">gis-account-avatar-chooser.ui</file>
+ <file preprocess="xml-stripblanks" alias="gis-account-page.ui">gis-account-page.ui</file>
+ <file preprocess="xml-stripblanks" alias="gis-account-page-local.ui">gis-account-page-local.ui</file>
+ <file preprocess="xml-stripblanks" alias="gis-account-page-enterprise.ui">gis-account-page-enterprise.ui</file>
+ </gresource>
+</gresources>
diff --git a/gnome-initial-setup/pages/account/gis-account-avatar-chooser.ui b/gnome-initial-setup/pages/account/gis-account-avatar-chooser.ui
new file mode 100644
index 0000000..dff58a1
--- /dev/null
+++ b/gnome-initial-setup/pages/account/gis-account-avatar-chooser.ui
@@ -0,0 +1,51 @@
+<?xml version="1.0"?>
+<interface>
+ <template class="UmPhotoDialog" parent="GtkPopover">
+ <property name="height-request">360</property>
+ <property name="width-request">480</property>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
+ <child>
+ <object class="GtkFlowBox" id="recent_pictures">
+ <property name="halign">start</property>
+ <property name="margin-top">20</property>
+ <property name="margin-start">20</property>
+ <property name="margin-end">20</property>
+ <property name="margin-bottom">0</property>
+ <property name="selection-mode">none</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkFlowBox" id="flowbox">
+ <property name="margin-top">12</property>
+ <property name="margin-bottom">12</property>
+ <property name="margin-start">12</property>
+ <property name="margin-end">12</property>
+ <property name="selection-mode">none</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="halign">GTK_ALIGN_CENTER</property>
+ <property name="margin-top">12</property>
+ <property name="margin-bottom">12</property>
+ <property name="margin-start">12</property>
+ <property name="margin-end">12</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkButton" id="take_picture_button">
+ <property name="visible">False</property>
+ <property name="label" translatable="yes">Take a Picture…</property>
+ <signal name="clicked" handler="webcam_icon_selected" swapped="yes"/>
+ <style>
+ <class name="suggested-action"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/gnome-initial-setup/pages/account/gis-account-page-enterprise.c b/gnome-initial-setup/pages/account/gis-account-page-enterprise.c
new file mode 100644
index 0000000..02a4f3b
--- /dev/null
+++ b/gnome-initial-setup/pages/account/gis-account-page-enterprise.c
@@ -0,0 +1,863 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2013 Red Hat
+ *
+ * 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 2 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/>.
+ *
+ * Written by:
+ * Jasper St. Pierre <jstpierre@mecheye.net>
+ */
+
+#include "config.h"
+
+#include "gis-account-page-enterprise.h"
+#include "gnome-initial-setup.h"
+
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+
+#include <act/act-user-manager.h>
+
+#include "um-realm-manager.h"
+#include "um-utils.h"
+
+#include "gis-page-header.h"
+
+static void join_show_prompt (GisAccountPageEnterprise *page,
+ GError *error);
+
+static void on_join_login (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data);
+
+static void on_realm_joined (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data);
+
+struct _GisAccountPageEnterprise
+{
+ AdwBin parent;
+
+ GtkWidget *header;
+ GtkWidget *login;
+ GtkWidget *password;
+ GtkWidget *domain;
+ GtkWidget *domain_entry;
+ GtkTreeModel *realms_model;
+
+ GtkWidget *join_dialog;
+ GtkWidget *join_name;
+ GtkWidget *join_password;
+ GtkWidget *join_domain;
+ GtkWidget *join_computer;
+
+ ActUserManager *act_client;
+ ActUser *act_user;
+
+ guint realmd_watch;
+ UmRealmManager *realm_manager;
+ gboolean domain_chosen;
+
+ /* Valid during apply */
+ UmRealmObject *realm;
+ GCancellable *cancellable;
+ gboolean join_prompted;
+
+ GisPageApplyCallback apply_complete_callback;
+ gpointer apply_complete_data;
+};
+
+G_DEFINE_TYPE (GisAccountPageEnterprise, gis_account_page_enterprise, ADW_TYPE_BIN);
+
+enum {
+ VALIDATION_CHANGED,
+ USER_CACHED,
+ LAST_SIGNAL,
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static void
+clear_password_validation_error (GtkWidget *entry)
+{
+ gtk_widget_remove_css_class (entry, "error");
+ gtk_widget_set_tooltip_text (entry, NULL);
+}
+
+static void
+set_password_validation_error (GtkWidget *entry,
+ const gchar *text)
+{
+ gtk_widget_add_css_class (entry, "error");
+ gtk_widget_set_tooltip_text (entry, text);
+}
+
+static void
+validation_changed (GisAccountPageEnterprise *page)
+{
+ g_signal_emit (page, signals[VALIDATION_CHANGED], 0);
+}
+
+static void
+apply_complete (GisAccountPageEnterprise *page,
+ gboolean valid)
+{
+ page->apply_complete_callback (NULL, valid, page->apply_complete_data);
+}
+
+static void
+show_error_dialog (GisAccountPageEnterprise *page,
+ const gchar *message,
+ GError *error)
+{
+ GtkWidget *dialog;
+
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ return;
+
+ dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (page))),
+ GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ "%s", message);
+
+ if (error != NULL) {
+ g_dbus_error_strip_remote_error (error);
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ "%s", error->message);
+ }
+
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (gtk_window_destroy),
+ NULL);
+ gtk_window_present (GTK_WINDOW (dialog));
+}
+
+gboolean
+gis_account_page_enterprise_validate (GisAccountPageEnterprise *page)
+{
+ const gchar *name;
+ gboolean valid_name;
+ gboolean valid_domain;
+ GtkTreeIter iter;
+
+ name = gtk_editable_get_text (GTK_EDITABLE (page->login));
+ valid_name = is_valid_name (name);
+
+ if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (page->domain), &iter)) {
+ gtk_tree_model_get (gtk_combo_box_get_model (GTK_COMBO_BOX (page->domain)),
+ &iter, 0, &name, -1);
+ } else {
+ name = gtk_editable_get_text (GTK_EDITABLE (page->domain_entry));
+ }
+
+ valid_domain = is_valid_name (name);
+ return valid_name && valid_domain;
+}
+
+static void
+on_permit_user_login (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GisAccountPageEnterprise *page = user_data;
+ UmRealmCommon *common;
+ GError *error = NULL;
+ gchar *login;
+
+ common = UM_REALM_COMMON (source);
+ um_realm_common_call_change_login_policy_finish (common, result, &error);
+ if (error == 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.
+ */
+ login = um_realm_calculate_login (common, gtk_editable_get_text (GTK_EDITABLE (page->login)));
+ g_return_if_fail (login != NULL);
+
+ g_debug ("Caching remote user: %s", login);
+
+ page->act_user = act_user_manager_cache_user (page->act_client, login, NULL);
+ act_user_set_account_type (page->act_user, ACT_USER_ACCOUNT_TYPE_ADMINISTRATOR);
+ g_signal_emit (page, signals[USER_CACHED], 0, page->act_user, gtk_editable_get_text (GTK_EDITABLE (page->password)));
+ apply_complete (page, TRUE);
+
+ g_free (login);
+ } else {
+ show_error_dialog (page, _("Failed to register account"), error);
+ g_message ("Couldn't permit logins on account: %s", error->message);
+ g_error_free (error);
+ apply_complete (page, FALSE);
+ }
+}
+
+static void
+enterprise_permit_user_login (GisAccountPageEnterprise *page, UmRealmObject *realm)
+{
+ UmRealmCommon *common;
+ gchar *login;
+ const gchar *add[2];
+ const gchar *remove[1];
+ GVariant *options;
+
+ common = um_realm_object_get_common (realm);
+
+ login = um_realm_calculate_login (common, gtk_editable_get_text (GTK_EDITABLE (page->login)));
+ g_return_if_fail (login != NULL);
+
+ add[0] = login;
+ add[1] = NULL;
+ remove[0] = NULL;
+
+ g_debug ("Permitting login for: %s", login);
+ options = g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0);
+
+ um_realm_common_call_change_login_policy (common, "",
+ add, remove, options,
+ page->cancellable,
+ on_permit_user_login,
+ page);
+
+ g_object_unref (common);
+ g_free (login);
+}
+
+static void
+on_set_static_hostname (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GisAccountPageEnterprise *page = user_data;
+ GError *error = NULL;
+ GVariant *retval;
+
+ retval = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, &error);
+ if (error != NULL) {
+ join_show_prompt (page, error);
+ g_error_free (error);
+ return;
+ }
+
+ g_variant_unref (retval);
+
+ /* Prompted for some admin credentials, try to use them to log in */
+ um_realm_login (page->realm,
+ gtk_editable_get_text (GTK_EDITABLE (page->join_name)),
+ gtk_editable_get_text (GTK_EDITABLE (page->join_password)),
+ page->cancellable, on_join_login, page);
+}
+
+static void
+on_join_response (GtkDialog *dialog,
+ gint response,
+ gpointer user_data)
+{
+ GisAccountPageEnterprise *page = user_data;
+ GDBusConnection *connection;
+ GError *error = NULL;
+ gchar hostname[128];
+ const gchar *name;
+
+ gtk_widget_hide (GTK_WIDGET (dialog));
+ if (response != GTK_RESPONSE_OK) {
+ apply_complete (page, FALSE);
+ return;
+ }
+
+ name = gtk_editable_get_text (GTK_EDITABLE (page->join_computer));
+ if (gethostname (hostname, sizeof (hostname)) == 0 &&
+ !g_str_equal (name, hostname)) {
+ g_debug ("Setting StaticHostname to '%s'", name);
+
+ connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, page->cancellable, &error);
+ if (error != NULL) {
+ apply_complete (page, FALSE);
+ g_warning ("Could not get DBus connection: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ g_dbus_connection_call (connection, "org.freedesktop.hostname1",
+ "/org/freedesktop/hostname1", "org.freedesktop.hostname1",
+ "SetStaticHostname",
+ g_variant_new ("(sb)", name, TRUE),
+ G_VARIANT_TYPE ("()"),
+ G_DBUS_CALL_FLAGS_NONE,
+ G_MAXINT, NULL, on_set_static_hostname, page);
+
+ } else {
+ name = gtk_editable_get_text (GTK_EDITABLE (page->join_name));
+ g_debug ("Logging in as admin user: %s", name);
+
+ /* Prompted for some admin credentials, try to use them to log in */
+ um_realm_login (page->realm, name,
+ gtk_editable_get_text (GTK_EDITABLE (page->join_password)),
+ NULL, on_join_login, page);
+ }
+}
+
+static void
+join_show_prompt (GisAccountPageEnterprise *page,
+ GError *error)
+{
+ UmRealmKerberosMembership *membership;
+ UmRealmKerberos *kerberos;
+ gchar hostname[128];
+ const gchar *name;
+
+ gtk_editable_set_text (GTK_EDITABLE (page->join_password), "");
+ gtk_widget_grab_focus (GTK_WIDGET (page->join_password));
+
+ kerberos = um_realm_object_get_kerberos (page->realm);
+ membership = um_realm_object_get_kerberos_membership (page->realm);
+
+ gtk_label_set_text (GTK_LABEL (page->join_domain),
+ um_realm_kerberos_get_domain_name (kerberos));
+
+ if (gethostname (hostname, sizeof (hostname)) == 0)
+ gtk_editable_set_text (GTK_EDITABLE (page->join_computer), hostname);
+
+ clear_entry_validation_error (GTK_ENTRY (page->join_name));
+ clear_password_validation_error (page->join_password);
+
+ if (!page->join_prompted) {
+ name = um_realm_kerberos_membership_get_suggested_administrator (membership);
+ if (name && !g_str_equal (name, "")) {
+ g_debug ("Suggesting admin user: %s", name);
+ gtk_editable_set_text (GTK_EDITABLE (page->join_name), name);
+ } else {
+ gtk_widget_grab_focus (GTK_WIDGET (page->join_name));
+ }
+
+ } else if (g_error_matches (error, UM_REALM_ERROR, UM_REALM_ERROR_BAD_HOSTNAME)) {
+ g_debug ("Bad host name: %s", error->message);
+ set_entry_validation_error (GTK_ENTRY (page->join_computer), error->message);
+
+ } else if (g_error_matches (error, UM_REALM_ERROR, UM_REALM_ERROR_BAD_PASSWORD)) {
+ g_debug ("Bad admin password: %s", error->message);
+ set_password_validation_error (page->join_password, error->message);
+
+ } else {
+ g_debug ("Admin login failure: %s", error->message);
+ g_dbus_error_strip_remote_error (error);
+ set_entry_validation_error (GTK_ENTRY (page->join_name), error->message);
+ }
+
+ g_debug ("Showing admin password dialog");
+ gtk_window_set_transient_for (GTK_WINDOW (page->join_dialog),
+ GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (page))));
+ gtk_window_set_modal (GTK_WINDOW (page->join_dialog), TRUE);
+ gtk_window_present (GTK_WINDOW (page->join_dialog));
+
+ page->join_prompted = TRUE;
+ g_object_unref (kerberos);
+ g_object_unref (membership);
+
+ /* And now we wait for on_join_response() */
+}
+
+static void
+on_join_login (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GisAccountPageEnterprise *page = user_data;
+ GError *error = NULL;
+ GBytes *creds;
+
+ um_realm_login_finish (page->realm, result, &creds, &error);
+
+ /* Logged in as admin successfully, use creds to join domain */
+ if (error == NULL) {
+ if (!um_realm_join_as_admin (page->realm,
+ gtk_editable_get_text (GTK_EDITABLE (page->join_name)),
+ gtk_editable_get_text (GTK_EDITABLE (page->join_password)),
+ creds, NULL, on_realm_joined, page)) {
+ show_error_dialog (page, _("No supported way to authenticate with this domain"), NULL);
+ g_message ("Authenticating as admin is not supported by the realm");
+ }
+
+ g_bytes_unref (creds);
+
+ /* Couldn't login as admin, show prompt again */
+ } else {
+ join_show_prompt (page, error);
+ g_message ("Couldn't log in as admin to join domain: %s", error->message);
+ g_error_free (error);
+ }
+}
+
+static void
+on_realm_joined (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GisAccountPageEnterprise *page = user_data;
+ GError *error = NULL;
+
+ um_realm_join_finish (page->realm, result, &error);
+
+ /* Yay, joined the domain, register the user locally */
+ if (error == NULL) {
+ g_debug ("Joining realm completed successfully");
+ enterprise_permit_user_login (page, page->realm);
+
+ /* Credential failure while joining domain, prompt for admin creds */
+ } else if (g_error_matches (error, UM_REALM_ERROR, UM_REALM_ERROR_BAD_LOGIN) ||
+ g_error_matches (error, UM_REALM_ERROR, UM_REALM_ERROR_BAD_PASSWORD) ||
+ g_error_matches (error, UM_REALM_ERROR, UM_REALM_ERROR_BAD_HOSTNAME)) {
+ g_debug ("Joining realm failed due to credentials or host name");
+
+ join_show_prompt (page, error);
+
+ /* Other failure */
+ } else {
+ show_error_dialog (page, _("Failed to join domain"), error);
+ g_message ("Failed to join the domain: %s", error->message);
+ apply_complete (page, FALSE);
+ }
+
+ g_clear_error (&error);
+}
+
+static void
+on_realm_login (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GisAccountPageEnterprise *page = user_data;
+ GError *error = NULL;
+ GBytes *creds = NULL;
+
+ um_realm_login_finish (page->realm, result, &creds, &error);
+
+ /*
+ * User login is valid, but cannot authenticate right now (eg: user needs
+ * to change password at next login etc.)
+ */
+ if (g_error_matches (error, UM_REALM_ERROR, UM_REALM_ERROR_CANNOT_AUTH)) {
+ g_clear_error (&error);
+ creds = NULL;
+ }
+
+ if (error == NULL) {
+
+ /* Already joined to the domain, just register this user */
+ if (um_realm_is_configured (page->realm)) {
+ g_debug ("Already joined to this realm");
+ enterprise_permit_user_login (page, page->realm);
+
+ /* Join the domain, try using the user's creds */
+ } else if (creds == NULL ||
+ !um_realm_join_as_user (page->realm,
+ gtk_editable_get_text (GTK_EDITABLE (page->login)),
+ gtk_editable_get_text (GTK_EDITABLE (page->password)),
+ creds, page->cancellable,
+ on_realm_joined,
+ page)) {
+
+ /* If we can't do user auth, try to authenticate as admin */
+ g_debug ("Cannot join with user credentials");
+
+ join_show_prompt (page, error);
+ }
+
+ g_bytes_unref (creds);
+
+ /* A problem with the user's login name or password */
+ } else if (g_error_matches (error, UM_REALM_ERROR, UM_REALM_ERROR_BAD_LOGIN)) {
+ g_debug ("Problem with the user's login: %s", error->message);
+ set_entry_validation_error (GTK_ENTRY (page->login), error->message);
+ gtk_widget_grab_focus (page->login);
+ apply_complete (page, FALSE);
+
+ } else if (g_error_matches (error, UM_REALM_ERROR, UM_REALM_ERROR_BAD_PASSWORD)) {
+ g_debug ("Problem with the user's password: %s", error->message);
+ set_password_validation_error (page->password, error->message);
+ gtk_widget_grab_focus (page->password);
+ apply_complete (page, FALSE);
+
+ /* Other login failure */
+ } else {
+ show_error_dialog (page, _("Failed to log into domain"), error);
+ g_message ("Couldn't log in as user: %s", error->message);
+ apply_complete (page, FALSE);
+ }
+
+ g_clear_error (&error);
+}
+
+static void
+enterprise_check_login (GisAccountPageEnterprise *page)
+{
+
+ g_assert (page->realm);
+
+ um_realm_login (page->realm,
+ gtk_editable_get_text (GTK_EDITABLE (page->login)),
+ gtk_editable_get_text (GTK_EDITABLE (page->password)),
+ page->cancellable,
+ on_realm_login,
+ page);
+}
+
+static void
+on_realm_discover_input (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GisAccountPageEnterprise *page = user_data;
+ GError *error = NULL;
+ GList *realms;
+
+ realms = um_realm_manager_discover_finish (page->realm_manager,
+ result, &error);
+
+ /* Found a realm, log user into domain */
+ if (error == NULL) {
+ g_assert (realms != NULL);
+ page->realm = g_object_ref (realms->data);
+ enterprise_check_login (page);
+ g_list_free_full (realms, g_object_unref);
+
+ } else {
+ /* The domain is likely invalid */
+ g_dbus_error_strip_remote_error (error);
+ g_message ("Couldn't discover domain: %s", error->message);
+ gtk_widget_grab_focus (page->domain_entry);
+ set_entry_validation_error (GTK_ENTRY (page->domain_entry), error->message);
+ apply_complete (page, FALSE);
+ g_error_free (error);
+ }
+}
+
+static void
+enterprise_add_user (GisAccountPageEnterprise *page)
+{
+ GtkTreeIter iter;
+
+ page->join_prompted = FALSE;
+ g_clear_object (&page->realm);
+
+ /* Already know about this realm, try to login as user */
+ if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (page->domain), &iter)) {
+ gtk_tree_model_get (gtk_combo_box_get_model (GTK_COMBO_BOX (page->domain)),
+ &iter, 1, &page->realm, -1);
+ enterprise_check_login (page);
+
+ /* Something the user typed, we need to discover realm */
+ } else {
+ um_realm_manager_discover (page->realm_manager,
+ gtk_editable_get_text (GTK_EDITABLE (page->domain_entry)),
+ page->cancellable,
+ on_realm_discover_input,
+ page);
+ }
+}
+
+gboolean
+gis_account_page_enterprise_apply (GisAccountPageEnterprise *page,
+ GCancellable *cancellable,
+ GisPageApplyCallback callback,
+ gpointer data)
+{
+ GisPage *account_page = GIS_PAGE (data);
+
+ /* Parental controls are not enterprise ready. It’s possible for them to have
+ * been enabled if the user enabled them, applied the account-local page, and
+ * then went back and decided to go all enterprise instead. */
+ gis_driver_set_parental_controls_enabled (account_page->driver, FALSE);
+
+ page->apply_complete_callback = callback;
+ page->apply_complete_data = data;
+ page->cancellable = g_object_ref (cancellable);
+ enterprise_add_user (page);
+ return TRUE;
+}
+
+static gchar *
+realm_get_name (UmRealmObject *realm)
+{
+ UmRealmCommon *common;
+ gchar *name;
+
+ common = um_realm_object_get_common (realm);
+ name = g_strdup (um_realm_common_get_name (common));
+ g_object_unref (common);
+
+ return name;
+}
+
+static gboolean
+model_contains_realm (GtkTreeModel *model,
+ const gchar *realm_name)
+{
+ gboolean contains = FALSE;
+ GtkTreeIter iter;
+ gboolean match;
+ gchar *name;
+ gboolean ret;
+
+ ret = gtk_tree_model_get_iter_first (model, &iter);
+ while (ret) {
+ gtk_tree_model_get (model, &iter, 0, &name, -1);
+ match = (g_strcmp0 (name, realm_name) == 0);
+ g_free (name);
+ if (match) {
+ g_debug ("ignoring duplicate realm: %s", realm_name);
+ contains = TRUE;
+ break;
+ }
+ ret = gtk_tree_model_iter_next (model, &iter);
+ }
+
+ return contains;
+}
+
+static void
+enterprise_add_realm (GisAccountPageEnterprise *page,
+ UmRealmObject *realm)
+{
+ GtkTreeIter iter;
+ gchar *name;
+
+ name = realm_get_name (realm);
+
+ /*
+ * Don't add a second realm if we already have one with this name.
+ * Sometimes realmd returns two realms for the same name, if it has
+ * different ways to use that realm. The first one that realmd
+ * returns is the one it prefers.
+ */
+
+ if (!model_contains_realm (GTK_TREE_MODEL (page->realms_model), name)) {
+ gtk_list_store_append (GTK_LIST_STORE (page->realms_model), &iter);
+ gtk_list_store_set (GTK_LIST_STORE (page->realms_model), &iter,
+ 0, name,
+ 1, realm,
+ -1);
+
+ if (!page->domain_chosen && um_realm_is_configured (realm))
+ gtk_combo_box_set_active_iter (GTK_COMBO_BOX (page->domain), &iter);
+
+ g_debug ("added realm to drop down: %s %s", name,
+ g_dbus_object_get_object_path (G_DBUS_OBJECT (realm)));
+ }
+
+ g_free (name);
+}
+
+static void
+on_manager_realm_added (UmRealmManager *manager,
+ UmRealmObject *realm,
+ gpointer user_data)
+{
+ GisAccountPageEnterprise *page = user_data;
+ enterprise_add_realm (page, realm);
+}
+
+static void
+on_realm_manager_created (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GisAccountPageEnterprise *page = user_data;
+ GError *error = NULL;
+ GList *realms, *l;
+
+ g_clear_object (&page->realm_manager);
+ page->realm_manager = um_realm_manager_new_finish (result, &error);
+
+ if (error != NULL) {
+ g_warning ("Couldn't contact realmd service: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ /* Lookup all the realm objects */
+ realms = um_realm_manager_get_realms (page->realm_manager);
+ for (l = realms; l != NULL; l = g_list_next (l))
+ enterprise_add_realm (page, l->data);
+
+ g_list_free (realms);
+ g_signal_connect (page->realm_manager, "realm-added",
+ G_CALLBACK (on_manager_realm_added), page);
+
+ /* When no realms try to discover a sensible default, triggers realm-added signal */
+ um_realm_manager_discover (page->realm_manager, "", NULL, NULL, NULL);
+ gtk_widget_set_visible (GTK_WIDGET (page), TRUE);
+}
+
+static void
+on_realmd_appeared (GDBusConnection *connection,
+ const gchar *name,
+ const gchar *name_owner,
+ gpointer user_data)
+{
+ GisAccountPageEnterprise *page = user_data;
+ um_realm_manager_new (NULL, on_realm_manager_created, page);
+}
+
+static void
+on_realmd_disappeared (GDBusConnection *unused1,
+ const gchar *unused2,
+ gpointer user_data)
+{
+ GisAccountPageEnterprise *page = user_data;
+
+ if (page->realm_manager != NULL) {
+ g_signal_handlers_disconnect_by_func (page->realm_manager,
+ on_manager_realm_added,
+ page);
+ g_clear_object (&page->realm_manager);
+ }
+
+ gtk_widget_set_visible (GTK_WIDGET (page), FALSE);
+}
+
+static void
+on_domain_changed (GtkComboBox *widget,
+ gpointer user_data)
+{
+ GisAccountPageEnterprise *page = user_data;
+
+ page->domain_chosen = TRUE;
+ validation_changed (page);
+ clear_entry_validation_error (GTK_ENTRY (gtk_combo_box_get_child (widget)));
+}
+
+static void
+on_entry_changed (GtkEditable *editable,
+ gpointer user_data)
+{
+ GisAccountPageEnterprise *page = user_data;
+ validation_changed (page);
+ clear_entry_validation_error (GTK_ENTRY (editable));
+}
+
+static void
+on_password_changed (GtkEditable *editable,
+ gpointer user_data)
+{
+ clear_password_validation_error (GTK_WIDGET (editable));
+}
+
+static void
+gis_account_page_enterprise_realize (GtkWidget *widget)
+{
+ GisAccountPageEnterprise *page = GIS_ACCOUNT_PAGE_ENTERPRISE (widget);
+ GtkWidget *gis_page;
+
+ gis_page = gtk_widget_get_ancestor (widget, GIS_TYPE_PAGE);
+ g_object_bind_property (gis_page, "small-screen",
+ page->header, "show-icon",
+ G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN);
+
+ GTK_WIDGET_CLASS (gis_account_page_enterprise_parent_class)->realize (widget);
+}
+
+static void
+gis_account_page_enterprise_constructed (GObject *object)
+{
+ GisAccountPageEnterprise *page = GIS_ACCOUNT_PAGE_ENTERPRISE (object);
+
+ G_OBJECT_CLASS (gis_account_page_enterprise_parent_class)->constructed (object);
+
+ page->act_client = act_user_manager_get_default ();
+
+ page->realmd_watch = g_bus_watch_name (G_BUS_TYPE_SYSTEM, "org.freedesktop.realmd",
+ G_BUS_NAME_WATCHER_FLAGS_AUTO_START,
+ on_realmd_appeared, on_realmd_disappeared,
+ page, NULL);
+
+ g_signal_connect (page->join_dialog, "response",
+ G_CALLBACK (on_join_response), page);
+ g_signal_connect (page->domain, "changed",
+ G_CALLBACK (on_domain_changed), page);
+ g_signal_connect (page->login, "changed",
+ G_CALLBACK (on_entry_changed), page);
+ g_signal_connect (page->password, "changed",
+ G_CALLBACK (on_password_changed), page);
+ g_signal_connect (page->join_password, "changed",
+ G_CALLBACK (on_password_changed), page);
+}
+
+static void
+gis_account_page_enterprise_dispose (GObject *object)
+{
+ GisAccountPageEnterprise *page = GIS_ACCOUNT_PAGE_ENTERPRISE (object);
+
+ if (page->realmd_watch)
+ g_bus_unwatch_name (page->realmd_watch);
+
+ page->realmd_watch = 0;
+
+ g_cancellable_cancel (page->cancellable);
+
+ g_clear_object (&page->realm_manager);
+ g_clear_object (&page->realm);
+ g_clear_object (&page->cancellable);
+
+ G_OBJECT_CLASS (gis_account_page_enterprise_parent_class)->dispose (object);
+}
+
+static void
+gis_account_page_enterprise_class_init (GisAccountPageEnterpriseClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->constructed = gis_account_page_enterprise_constructed;
+ object_class->dispose = gis_account_page_enterprise_dispose;
+
+ widget_class->realize = gis_account_page_enterprise_realize;
+
+ gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (klass), "/org/gnome/initial-setup/gis-account-page-enterprise.ui");
+
+ gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPageEnterprise, login);
+ gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPageEnterprise, password);
+ gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPageEnterprise, domain);
+ gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPageEnterprise, realms_model);
+ gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPageEnterprise, header);
+
+ gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPageEnterprise, join_dialog);
+ gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPageEnterprise, join_name);
+ gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPageEnterprise, join_password);
+ gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPageEnterprise, join_domain);
+ gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPageEnterprise, join_computer);
+
+ signals[VALIDATION_CHANGED] = g_signal_new ("validation-changed", GIS_TYPE_ACCOUNT_PAGE_ENTERPRISE,
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+ signals[USER_CACHED] = g_signal_new ("user-cached", GIS_TYPE_ACCOUNT_PAGE_ENTERPRISE,
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 2, ACT_TYPE_USER, G_TYPE_STRING);
+}
+
+static void
+gis_account_page_enterprise_init (GisAccountPageEnterprise *page)
+{
+ g_type_ensure (GIS_TYPE_PAGE_HEADER);
+ gtk_widget_init_template (GTK_WIDGET (page));
+
+ page->domain_entry = gtk_combo_box_get_child (GTK_COMBO_BOX (page->domain));
+}
+
+void
+gis_account_page_enterprise_shown (GisAccountPageEnterprise *page)
+{
+ gtk_widget_grab_focus (page->domain_entry);
+}
diff --git a/gnome-initial-setup/pages/account/gis-account-page-enterprise.h b/gnome-initial-setup/pages/account/gis-account-page-enterprise.h
new file mode 100644
index 0000000..c156f44
--- /dev/null
+++ b/gnome-initial-setup/pages/account/gis-account-page-enterprise.h
@@ -0,0 +1,42 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2013 Red Hat
+ *
+ * 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 2 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/>.
+ *
+ * Written by:
+ * Jasper St. Pierre <jstpierre@mecheye.net>
+ */
+
+#pragma once
+
+#include <adwaita.h>
+
+/* For GisPageApplyCallback */
+#include "gis-page.h"
+
+G_BEGIN_DECLS
+
+#define GIS_TYPE_ACCOUNT_PAGE_ENTERPRISE (gis_account_page_enterprise_get_type ())
+G_DECLARE_FINAL_TYPE (GisAccountPageEnterprise, gis_account_page_enterprise, GIS, ACCOUNT_PAGE_ENTERPRISE, AdwBin)
+
+gboolean gis_account_page_enterprise_validate (GisAccountPageEnterprise *enterprise);
+gboolean gis_account_page_enterprise_apply (GisAccountPageEnterprise *enterprise,
+ GCancellable *cancellable,
+ GisPageApplyCallback callback,
+ gpointer data);
+void gis_account_page_enterprise_shown (GisAccountPageEnterprise *enterprise);
+
+G_END_DECLS
+
diff --git a/gnome-initial-setup/pages/account/gis-account-page-enterprise.ui b/gnome-initial-setup/pages/account/gis-account-page-enterprise.ui
new file mode 100644
index 0000000..e5ef0db
--- /dev/null
+++ b/gnome-initial-setup/pages/account/gis-account-page-enterprise.ui
@@ -0,0 +1,280 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="GisAccountPageEnterprise" parent="AdwBin">
+ <child>
+ <object class="GtkBox" id="area">
+ <property name="orientation">vertical</property>
+ <property name="valign">fill</property>
+ <child>
+ <object class="GisPageHeader" id="header">
+ <property name="margin_bottom">26</property>
+ <property name="margin_top">24</property>
+ <property name="title" translatable="yes">Enterprise Login</property>
+ <property name="subtitle" translatable="yes">Enterprise login allows an existing centrally managed user account to be used on this device. You can also use this account to access company resources on the internet.</property>
+ <property name="icon_name">dialog-password-symbolic</property>
+ <property name="show_icon">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkGrid" id="form">
+ <property name="row_spacing">12</property>
+ <property name="column_spacing">12</property>
+ <property name="margin_bottom">32</property>
+ <child>
+ <object class="GtkLabel" id="label4">
+ <property name="halign">end</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">_Domain</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">domain</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label8">
+ <property name="halign">end</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">_Username</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">login</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">3</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label9">
+ <property name="halign">end</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">_Password</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">password</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">4</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="login">
+ <property name="hexpand">True</property>
+ <property name="max-length">255</property>
+ <property name="width-chars">25</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">3</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkPasswordEntry" id="password">
+ <property name="hexpand">True</property>
+ <property name="width-chars">25</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">4</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="domain">
+ <property name="hexpand">True</property>
+ <property name="model">realms_model</property>
+ <property name="has_entry">True</property>
+ <property name="entry_text_column">0</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label10">
+ <property name="margin_bottom">12</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Enterprise domain or realm name</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">2</property>
+ </layout>
+ <attributes>
+ <attribute name="scale" value="0.8"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="filler">
+ <layout>
+ <property name="column">2</property>
+ <property name="row">4</property>
+ </layout>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+ <object class="GtkDialog" id="join_dialog">
+ <property name="resizable">False</property>
+ <property name="modal">True</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="title" translatable="yes">Domain Administrator Login</property>
+ <child internal-child="content_area">
+ <object class="GtkBox" id="dialog-vbox1">
+ <property name="orientation">vertical</property>
+ <property name="margin-top">18</property>
+ <property name="margin-bottom">18</property>
+ <property name="margin-start">18</property>
+ <property name="margin-end">18</property>
+ <property name="spacing">10</property>
+ <child>
+ <object class="GtkLabel" id="label12">
+ <property name="xalign">0.5</property>
+ <property name="yalign">0</property>
+ <property name="wrap">True</property>
+ <property name="max-width-chars">60</property>
+ <property name="label" translatable="yes">In order to use enterprise logins, this computer needs to be enrolled in a domain. Please have your network administrator type the domain password here, and choose a unique computer name for your computer.</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkGrid" id="grid1">
+ <property name="margin-start">12</property>
+ <property name="hexpand">True</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="label13">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">_Domain</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">join_domain</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ </layout>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="join_domain">
+ <property name="margin_top">5</property>
+ <property name="margin_bottom">5</property>
+ <property name="xalign">0</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label18">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">_Computer</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">join_computer</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">1</property>
+ </layout>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="join_computer">
+ <property name="hexpand">True</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label14">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Administrator _Name</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">join_name</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">2</property>
+ </layout>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="join_name">
+ <property name="hexpand">True</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label15">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Administrator Password</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">join_password</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">3</property>
+ </layout>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkPasswordEntry" id="join_password">
+ <property name="hexpand">True</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">3</property>
+ </layout>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="action">
+ <object class="GtkButton" id="button_cancel">
+ <property name="label">_Cancel</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+ <child type="action">
+ <object class="GtkButton" id="button_ok">
+ <property name="label" translatable="yes">C_ontinue</property>
+ <property name="use_underline">True</property>
+ <style>
+ <class name="suggested-action"/>
+ </style>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-5" default="true">button_ok</action-widget>
+ <action-widget response="-6">button_cancel</action-widget>
+ </action-widgets>
+ </object>
+ <object class="GtkListStore" id="realms_model">
+ <columns>
+ <!-- column-name gchararray -->
+ <column type="gchararray"/>
+ <!-- column-name gobject -->
+ <column type="GObject"/>
+ </columns>
+ </object>
+</interface>
diff --git a/gnome-initial-setup/pages/account/gis-account-page-local.c b/gnome-initial-setup/pages/account/gis-account-page-local.c
new file mode 100644
index 0000000..21bb597
--- /dev/null
+++ b/gnome-initial-setup/pages/account/gis-account-page-local.c
@@ -0,0 +1,721 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2013 Red Hat
+ *
+ * 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 2 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/>.
+ *
+ * Written by:
+ * Jasper St. Pierre <jstpierre@mecheye.net>
+ */
+
+#include "config.h"
+
+#include "gis-page.h"
+#include "gis-account-page-local.h"
+#include "gnome-initial-setup.h"
+
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+
+#include <string.h>
+#include <act/act-user-manager.h>
+#include "um-utils.h"
+#include "um-photo-dialog.h"
+
+#include "gis-page-header.h"
+
+#define GOA_API_IS_SUBJECT_TO_CHANGE
+#include <goa/goa.h>
+
+#include <rest/rest-proxy.h>
+#include <json-glib/json-glib.h>
+
+#define VALIDATION_TIMEOUT 600
+
+struct _GisAccountPageLocal
+{
+ AdwBin parent;
+
+ GtkWidget *avatar_button;
+ GtkWidget *avatar_image;
+ GtkWidget *header;
+ GtkWidget *fullname_entry;
+ GtkWidget *username_combo;
+ GtkWidget *enable_parental_controls_box;
+ GtkWidget *enable_parental_controls_check_button;
+ gboolean has_custom_username;
+ GtkWidget *username_explanation;
+ UmPhotoDialog *photo_dialog;
+
+ gint timeout_id;
+
+ GdkPixbuf *avatar_pixbuf;
+ gchar *avatar_filename;
+
+ ActUserManager *act_client;
+
+ GoaClient *goa_client;
+
+ gboolean valid_name;
+ gboolean valid_username;
+ ActUserAccountType account_type;
+};
+
+G_DEFINE_TYPE (GisAccountPageLocal, gis_account_page_local, ADW_TYPE_BIN);
+
+enum {
+ VALIDATION_CHANGED,
+ MAIN_USER_CREATED,
+ PARENT_USER_CREATED,
+ CONFIRM,
+ LAST_SIGNAL,
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static void
+validation_changed (GisAccountPageLocal *page)
+{
+ g_signal_emit (page, signals[VALIDATION_CHANGED], 0);
+}
+
+static gboolean
+get_profile_sync (const gchar *access_token,
+ gchar **out_name,
+ gchar **out_picture,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GError *identity_error;
+ RestProxy *proxy;
+ RestProxyCall *call;
+ JsonParser *parser;
+ JsonObject *json_object;
+ gboolean ret;
+
+ ret = FALSE;
+
+ identity_error = NULL;
+ proxy = NULL;
+ call = NULL;
+ parser = NULL;
+
+ /* TODO: cancellable */
+
+ proxy = rest_proxy_new ("https://www.googleapis.com/oauth2/v2/userinfo", FALSE);
+ call = rest_proxy_new_call (proxy);
+ rest_proxy_call_set_method (call, "GET");
+ rest_proxy_call_add_param (call, "access_token", access_token);
+
+ if (!rest_proxy_call_sync (call, error))
+ goto out;
+
+ if (rest_proxy_call_get_status_code (call) != 200)
+ {
+ g_set_error (error,
+ GOA_ERROR,
+ GOA_ERROR_FAILED,
+ "Expected status 200 when requesting your identity, instead got status %d (%s)",
+ rest_proxy_call_get_status_code (call),
+ rest_proxy_call_get_status_message (call));
+ goto out;
+ }
+
+ parser = json_parser_new ();
+ if (!json_parser_load_from_data (parser,
+ rest_proxy_call_get_payload (call),
+ rest_proxy_call_get_payload_length (call),
+ &identity_error))
+ {
+ g_warning ("json_parser_load_from_data() failed: %s (%s, %d)",
+ identity_error->message,
+ g_quark_to_string (identity_error->domain),
+ identity_error->code);
+ g_set_error (error,
+ GOA_ERROR,
+ GOA_ERROR_FAILED,
+ "Could not parse response");
+ goto out;
+ }
+
+ ret = TRUE;
+
+ json_object = json_node_get_object (json_parser_get_root (parser));
+ if (out_name != NULL)
+ *out_name = g_strdup (json_object_get_string_member (json_object, "name"));
+
+ if (out_picture != NULL)
+ *out_picture = g_strdup (json_object_get_string_member (json_object, "picture"));
+
+ out:
+ g_clear_error (&identity_error);
+ if (call != NULL)
+ g_object_unref (call);
+ if (proxy != NULL)
+ g_object_unref (proxy);
+
+ return ret;
+}
+
+static void
+prepopulate_account_page (GisAccountPageLocal *page)
+{
+ gchar *name = NULL;
+ gchar *picture = NULL;
+ GdkPixbuf *pixbuf = NULL;
+
+ if (page->goa_client) {
+ GList *accounts, *l;
+ accounts = goa_client_get_accounts (page->goa_client);
+ for (l = accounts; l != NULL; l = l->next) {
+ GoaOAuth2Based *oa2;
+ oa2 = goa_object_get_oauth2_based (GOA_OBJECT (l->data));
+ if (oa2) {
+ gchar *token = NULL;
+ GError *error = NULL;
+ if (!goa_oauth2_based_call_get_access_token_sync (oa2, &token, NULL, NULL, &error))
+ {
+ g_warning ("Couldn't get oauth2 token: %s", error->message);
+ g_error_free (error);
+ }
+ else if (!get_profile_sync (token, &name, &picture, NULL, &error))
+ {
+ g_warning ("Couldn't get profile information: %s", error->message);
+ g_error_free (error);
+ }
+ /* FIXME: collect information from more than one account
+ * and present at least the pictures in the avatar chooser
+ */
+ break;
+ }
+ }
+ g_list_free_full (accounts, (GDestroyNotify) g_object_unref);
+ }
+
+ if (name) {
+ g_object_set (page->header, "subtitle", _("Please check the name and username. You can choose a picture too."), NULL);
+ gtk_editable_set_text (GTK_EDITABLE (page->fullname_entry), name);
+ }
+
+ if (picture) {
+ GFile *file;
+ GFileInputStream *stream;
+ GError *error = NULL;
+ file = g_file_new_for_uri (picture);
+ stream = g_file_read (file, NULL, &error);
+ if (!stream)
+ {
+ g_warning ("Failed to read picture %s: %s", picture, error->message);
+ g_error_free (error);
+ }
+ else
+ {
+ pixbuf = gdk_pixbuf_new_from_stream_at_scale (G_INPUT_STREAM (stream), -1, 96, TRUE, NULL, NULL);
+ g_object_unref (stream);
+ }
+ g_object_unref (file);
+ }
+
+ if (pixbuf) {
+ GdkPixbuf *rounded = round_image (pixbuf);
+
+ gtk_image_set_from_pixbuf (GTK_IMAGE (page->avatar_image), rounded);
+ g_object_unref (rounded);
+ page->avatar_pixbuf = pixbuf;
+ }
+
+ g_free (name);
+ g_free (picture);
+}
+
+static void
+accounts_changed (GoaClient *client, GoaObject *object, gpointer data)
+{
+ GisAccountPageLocal *page = data;
+
+ prepopulate_account_page (page);
+}
+
+static gboolean
+validate (GisAccountPageLocal *page)
+{
+ GtkWidget *entry;
+ const gchar *name, *username;
+ gboolean parental_controls_enabled;
+ gchar *tip;
+
+ g_clear_handle_id (&page->timeout_id, g_source_remove);
+
+ entry = gtk_combo_box_get_child (GTK_COMBO_BOX (page->username_combo));
+
+ name = gtk_editable_get_text (GTK_EDITABLE (page->fullname_entry));
+ username = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (page->username_combo));
+#ifdef HAVE_PARENTAL_CONTROLS
+ parental_controls_enabled = gtk_check_button_get_active (GTK_CHECK_BUTTON (page->enable_parental_controls_check_button));
+#else
+ parental_controls_enabled = FALSE;
+#endif
+
+ page->valid_name = is_valid_name (name);
+ if (page->valid_name)
+ set_entry_validation_checkmark (GTK_ENTRY (page->fullname_entry));
+
+ page->valid_username = is_valid_username (username, parental_controls_enabled, &tip);
+ if (page->valid_username)
+ set_entry_validation_checkmark (GTK_ENTRY (entry));
+
+ gtk_label_set_text (GTK_LABEL (page->username_explanation), tip);
+ g_free (tip);
+
+ um_photo_dialog_generate_avatar (page->photo_dialog, name);
+
+ validation_changed (page);
+
+ return G_SOURCE_REMOVE;
+}
+
+static gboolean
+on_focusout (GisAccountPageLocal *page)
+{
+ validate (page);
+
+ return FALSE;
+}
+
+static void
+fullname_changed (GtkWidget *w,
+ GParamSpec *pspec,
+ GisAccountPageLocal *page)
+{
+ GtkWidget *entry;
+ GtkTreeModel *model;
+ const char *name;
+
+ name = gtk_editable_get_text (GTK_EDITABLE (w));
+
+ entry = gtk_combo_box_get_child (GTK_COMBO_BOX (page->username_combo));
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (page->username_combo));
+
+ gtk_list_store_clear (GTK_LIST_STORE (model));
+
+ if ((name == NULL || strlen (name) == 0) && !page->has_custom_username) {
+ gtk_editable_set_text (GTK_EDITABLE (entry), "");
+ }
+ else if (name != NULL && strlen (name) != 0) {
+ generate_username_choices (name, GTK_LIST_STORE (model));
+ if (!page->has_custom_username)
+ gtk_combo_box_set_active (GTK_COMBO_BOX (page->username_combo), 0);
+ }
+
+ clear_entry_validation_error (GTK_ENTRY (w));
+
+ page->valid_name = FALSE;
+
+ /* username_changed() is called consequently due to changes */
+}
+
+static void
+username_changed (GtkComboBoxText *combo,
+ GisAccountPageLocal *page)
+{
+ GtkWidget *entry;
+ const gchar *username;
+
+ entry = gtk_combo_box_get_child (GTK_COMBO_BOX (combo));
+ username = gtk_editable_get_text (GTK_EDITABLE (entry));
+ if (*username == '\0')
+ page->has_custom_username = FALSE;
+ else if (gtk_widget_has_focus (entry) ||
+ gtk_combo_box_get_active (GTK_COMBO_BOX (page->username_combo)) > 0)
+ page->has_custom_username = TRUE;
+
+ clear_entry_validation_error (GTK_ENTRY (entry));
+
+ page->valid_username = FALSE;
+ validation_changed (page);
+
+ if (page->timeout_id != 0)
+ g_source_remove (page->timeout_id);
+ page->timeout_id = g_timeout_add (VALIDATION_TIMEOUT, (GSourceFunc)validate, page);
+}
+
+static void
+avatar_callback (GdkPixbuf *pixbuf,
+ const gchar *filename,
+ gpointer user_data)
+{
+ GisAccountPageLocal *page = user_data;
+ g_autoptr(GdkPixbuf) tmp = NULL;
+ g_autoptr(GdkPixbuf) rounded = NULL;
+
+ g_clear_object (&page->avatar_pixbuf);
+ g_clear_pointer (&page->avatar_filename, g_free);
+
+ if (pixbuf) {
+ page->avatar_pixbuf = g_object_ref (pixbuf);
+ rounded = round_image (pixbuf);
+ }
+ else if (filename) {
+ page->avatar_filename = g_strdup (filename);
+ tmp = gdk_pixbuf_new_from_file_at_size (filename, 96, 96, NULL);
+
+ if (tmp != NULL)
+ rounded = round_image (tmp);
+ }
+
+ if (rounded != NULL) {
+ gtk_image_set_from_pixbuf (GTK_IMAGE (page->avatar_image), rounded);
+ }
+ else {
+ /* Fallback. */
+ gtk_image_set_pixel_size (GTK_IMAGE (page->avatar_image), 96);
+ gtk_image_set_from_icon_name (GTK_IMAGE (page->avatar_image), "avatar-default-symbolic");
+ }
+}
+
+static void
+confirm (GisAccountPageLocal *page)
+{
+ if (gis_account_page_local_validate (page))
+ g_signal_emit (page, signals[CONFIRM], 0);
+}
+
+static void
+enable_parental_controls_check_button_toggled_cb (GtkCheckButton *check_button,
+ gpointer user_data)
+{
+ GisAccountPageLocal *page = GIS_ACCOUNT_PAGE_LOCAL (user_data);
+ gboolean parental_controls_enabled = gtk_check_button_get_active (GTK_CHECK_BUTTON (page->enable_parental_controls_check_button));
+
+ /* This sets the account type of the main user. When we save_data(), we create
+ * two users if parental controls are enabled: the first user is always an
+ * admin, and the second user is the main user using this @account_type. */
+ page->account_type = parental_controls_enabled ? ACT_USER_ACCOUNT_TYPE_STANDARD : ACT_USER_ACCOUNT_TYPE_ADMINISTRATOR;
+
+ validate (page);
+}
+
+static void
+track_focus_out (GisAccountPageLocal *page,
+ GtkWidget *widget)
+{
+ GtkEventController *focus_controller;
+
+ focus_controller = gtk_event_controller_focus_new ();
+ gtk_widget_add_controller (widget, focus_controller);
+
+ g_signal_connect_swapped (focus_controller, "leave", G_CALLBACK (on_focusout), page);
+}
+
+
+static void
+gis_account_page_local_constructed (GObject *object)
+{
+ GisAccountPageLocal *page = GIS_ACCOUNT_PAGE_LOCAL (object);
+
+ G_OBJECT_CLASS (gis_account_page_local_parent_class)->constructed (object);
+
+ page->act_client = act_user_manager_get_default ();
+
+ g_signal_connect (page->fullname_entry, "notify::text",
+ G_CALLBACK (fullname_changed), page);
+ track_focus_out (page, page->fullname_entry);
+
+ g_signal_connect_swapped (page->fullname_entry, "activate",
+ G_CALLBACK (validate), page);
+ g_signal_connect (page->username_combo, "changed",
+ G_CALLBACK (username_changed), page);
+ track_focus_out (page, page->username_combo);
+
+ g_signal_connect_swapped (gtk_combo_box_get_child (GTK_COMBO_BOX (page->username_combo)),
+ "activate", G_CALLBACK (confirm), page);
+ g_signal_connect_swapped (page->fullname_entry, "activate",
+ G_CALLBACK (confirm), page);
+ g_signal_connect (page->enable_parental_controls_check_button, "toggled",
+ G_CALLBACK (enable_parental_controls_check_button_toggled_cb), page);
+
+ /* Disable parental controls if support is not compiled in. */
+#ifndef HAVE_PARENTAL_CONTROLS
+ gtk_widget_hide (page->enable_parental_controls_box);
+#endif
+
+ page->valid_name = FALSE;
+ page->valid_username = FALSE;
+
+ /* FIXME: change this for a large deployment scenario; maybe through a GSetting? */
+ page->account_type = ACT_USER_ACCOUNT_TYPE_ADMINISTRATOR;
+
+ g_object_set (page->header, "subtitle", _("We need a few details to complete setup."), NULL);
+ gtk_editable_set_text (GTK_EDITABLE (page->fullname_entry), "");
+ gtk_list_store_clear (GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX (page->username_combo))));
+ page->has_custom_username = FALSE;
+
+ gtk_image_set_pixel_size (GTK_IMAGE (page->avatar_image), 96);
+ gtk_image_set_from_icon_name (GTK_IMAGE (page->avatar_image), "avatar-default-symbolic");
+
+ page->goa_client = goa_client_new_sync (NULL, NULL);
+ if (page->goa_client) {
+ g_signal_connect (page->goa_client, "account-added",
+ G_CALLBACK (accounts_changed), page);
+ g_signal_connect (page->goa_client, "account-removed",
+ G_CALLBACK (accounts_changed), page);
+ prepopulate_account_page (page);
+ }
+
+ page->photo_dialog = um_photo_dialog_new (avatar_callback, page);
+ um_photo_dialog_generate_avatar (page->photo_dialog, "");
+ gtk_menu_button_set_popover (GTK_MENU_BUTTON (page->avatar_button),
+ GTK_WIDGET (page->photo_dialog));
+
+ validate (page);
+}
+
+static void
+gis_account_page_local_dispose (GObject *object)
+{
+ GisAccountPageLocal *page = GIS_ACCOUNT_PAGE_LOCAL (object);
+
+ g_clear_object (&page->goa_client);
+ g_clear_object (&page->avatar_pixbuf);
+ g_clear_pointer (&page->avatar_filename, g_free);
+ g_clear_handle_id (&page->timeout_id, g_source_remove);
+
+ G_OBJECT_CLASS (gis_account_page_local_parent_class)->dispose (object);
+}
+
+static void
+set_user_avatar (GisAccountPageLocal *page,
+ ActUser *user)
+{
+ GFile *file = NULL;
+ GFileIOStream *io_stream = NULL;
+ GOutputStream *stream = NULL;
+ GError *error = NULL;
+
+ if (page->avatar_filename != NULL) {
+ act_user_set_icon_file (user, page->avatar_filename);
+ return;
+ }
+
+ if (page->avatar_pixbuf == NULL) {
+ return;
+ }
+
+ file = g_file_new_tmp ("usericonXXXXXX", &io_stream, &error);
+ if (error != NULL)
+ goto out;
+
+ stream = g_io_stream_get_output_stream (G_IO_STREAM (io_stream));
+ if (!gdk_pixbuf_save_to_stream (page->avatar_pixbuf, stream, "png", NULL, &error, NULL))
+ goto out;
+
+ act_user_set_icon_file (user, g_file_get_path (file));
+
+ out:
+ if (error != NULL) {
+ g_warning ("failed to save image: %s", error->message);
+ g_error_free (error);
+ }
+ g_clear_object (&io_stream);
+ g_clear_object (&file);
+}
+
+static gboolean
+local_create_user (GisAccountPageLocal *local,
+ GisPage *page,
+ GError **error)
+{
+ const gchar *username;
+ const gchar *fullname;
+ gboolean parental_controls_enabled;
+ g_autoptr(ActUser) main_user = NULL;
+ g_autoptr(ActUser) parent_user = NULL;
+
+ username = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (local->username_combo));
+ fullname = gtk_editable_get_text (GTK_EDITABLE (local->fullname_entry));
+ parental_controls_enabled = gis_driver_get_parental_controls_enabled (page->driver);
+
+ /* Always create the admin user first, in case of failure part-way through
+ * this function, which would leave us with no admin user at all. */
+ if (parental_controls_enabled) {
+ g_autoptr(GError) local_error = NULL;
+ g_autoptr(GDBusConnection) connection = NULL;
+ const gchar *parent_username = "administrator";
+ const gchar *parent_fullname = _("Administrator");
+
+ parent_user = act_user_manager_create_user (local->act_client, parent_username, parent_fullname, ACT_USER_ACCOUNT_TYPE_ADMINISTRATOR, error);
+ if (parent_user == NULL)
+ {
+ g_prefix_error (error,
+ _("Failed to create user '%s': "),
+ parent_username);
+ return FALSE;
+ }
+
+ /* Make the admin account usable in case g-i-s crashes. If all goes
+ * according to plan a password will be set on it in gis-password-page.c */
+ act_user_set_password_mode (parent_user, ACT_USER_PASSWORD_MODE_SET_AT_LOGIN);
+
+ /* Mark it as the parent user account.
+ * FIXME: This should be async. */
+ connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &local_error);
+ if (connection != NULL) {
+ g_dbus_connection_call_sync (connection,
+ "org.freedesktop.Accounts",
+ act_user_get_object_path (parent_user),
+ "org.freedesktop.DBus.Properties",
+ "Set",
+ g_variant_new ("(ssv)",
+ "com.endlessm.ParentalControls.AccountInfo",
+ "IsParent",
+ g_variant_new_boolean (TRUE)),
+ NULL, /* reply type */
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, /* default timeout */
+ NULL, /* cancellable */
+ &local_error);
+ }
+ if (local_error != NULL) {
+ /* Make this non-fatal, since the correct accounts-service interface
+ * might not be installed, depending on which version of malcontent is installed. */
+ g_warning ("Failed to mark user as parent: %s", local_error->message);
+ g_clear_error (&local_error);
+ }
+
+ g_signal_emit (local, signals[PARENT_USER_CREATED], 0, parent_user, "");
+ }
+
+ /* Now create the main user. */
+ main_user = act_user_manager_create_user (local->act_client, username, fullname, local->account_type, error);
+ if (main_user == NULL)
+ {
+ g_prefix_error (error,
+ _("Failed to create user '%s': "),
+ username);
+ /* FIXME: Could we delete the @parent_user at this point to reset the state
+ * and allow g-i-s to be run again after a reboot? */
+ return FALSE;
+ }
+
+ set_user_avatar (local, main_user);
+
+ g_signal_emit (local, signals[MAIN_USER_CREATED], 0, main_user, "");
+
+ return TRUE;
+}
+
+static void
+gis_account_page_local_class_init (GisAccountPageLocalClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (klass), "/org/gnome/initial-setup/gis-account-page-local.ui");
+
+ gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPageLocal, avatar_button);
+ gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPageLocal, avatar_image);
+ gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPageLocal, header);
+ gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPageLocal, fullname_entry);
+ gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPageLocal, username_combo);
+ gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPageLocal, username_explanation);
+ gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPageLocal, enable_parental_controls_box);
+ gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPageLocal, enable_parental_controls_check_button);
+
+ object_class->constructed = gis_account_page_local_constructed;
+ object_class->dispose = gis_account_page_local_dispose;
+
+ signals[VALIDATION_CHANGED] = g_signal_new ("validation-changed", GIS_TYPE_ACCOUNT_PAGE_LOCAL,
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ signals[MAIN_USER_CREATED] = g_signal_new ("main-user-created", GIS_TYPE_ACCOUNT_PAGE_LOCAL,
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 2, ACT_TYPE_USER, G_TYPE_STRING);
+
+ signals[PARENT_USER_CREATED] = g_signal_new ("parent-user-created", GIS_TYPE_ACCOUNT_PAGE_LOCAL,
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 2, ACT_TYPE_USER, G_TYPE_STRING);
+
+ signals[CONFIRM] = g_signal_new ("confirm", GIS_TYPE_ACCOUNT_PAGE_LOCAL,
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+}
+
+static void
+gis_account_page_local_init (GisAccountPageLocal *page)
+{
+ g_type_ensure (GIS_TYPE_PAGE_HEADER);
+ gtk_widget_init_template (GTK_WIDGET (page));
+}
+
+gboolean
+gis_account_page_local_validate (GisAccountPageLocal *page)
+{
+ return page->valid_name && page->valid_username;
+}
+
+gboolean
+gis_account_page_local_create_user (GisAccountPageLocal *local,
+ GisPage *page,
+ GError **error)
+{
+ return local_create_user (local, page, error);
+}
+
+gboolean
+gis_account_page_local_apply (GisAccountPageLocal *local, GisPage *page)
+{
+ const gchar *username, *full_name;
+ gboolean parental_controls_enabled;
+
+ username = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (local->username_combo));
+ gis_driver_set_username (GIS_PAGE (page)->driver, username);
+
+ full_name = gtk_editable_get_text (GTK_EDITABLE (local->fullname_entry));
+ gis_driver_set_full_name (GIS_PAGE (page)->driver, full_name);
+
+ if (local->avatar_pixbuf != NULL)
+ {
+ g_autoptr(GdkTexture) texture = NULL;
+
+ texture = gdk_texture_new_for_pixbuf (local->avatar_pixbuf);
+ gis_driver_set_avatar (GIS_PAGE (page)->driver, GDK_PAINTABLE (texture));
+ }
+ else if (local->avatar_filename != NULL)
+ {
+ g_autoptr(GdkTexture) texture = NULL;
+ g_autoptr(GError) error = NULL;
+
+ texture = gdk_texture_new_from_filename (local->avatar_filename, &error);
+
+ if (!error)
+ gis_driver_set_avatar (GIS_PAGE (page)->driver, GDK_PAINTABLE (texture));
+ else
+ g_warning ("Error loading avatar: %s", error->message);
+ }
+
+#ifdef HAVE_PARENTAL_CONTROLS
+ parental_controls_enabled = gtk_check_button_get_active (GTK_CHECK_BUTTON (local->enable_parental_controls_check_button));
+#else
+ parental_controls_enabled = FALSE;
+#endif
+ gis_driver_set_parental_controls_enabled (GIS_PAGE (page)->driver, parental_controls_enabled);
+
+ return FALSE;
+}
+
+void
+gis_account_page_local_shown (GisAccountPageLocal *local)
+{
+ gtk_widget_grab_focus (local->fullname_entry);
+}
diff --git a/gnome-initial-setup/pages/account/gis-account-page-local.h b/gnome-initial-setup/pages/account/gis-account-page-local.h
new file mode 100644
index 0000000..9d8dea0
--- /dev/null
+++ b/gnome-initial-setup/pages/account/gis-account-page-local.h
@@ -0,0 +1,39 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2013 Red Hat
+ *
+ * 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 2 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/>.
+ *
+ * Written by:
+ * Jasper St. Pierre <jstpierre@mecheye.net>
+ */
+
+#pragma once
+
+#include <adwaita.h>
+
+G_BEGIN_DECLS
+
+#define GIS_TYPE_ACCOUNT_PAGE_LOCAL (gis_account_page_local_get_type ())
+G_DECLARE_FINAL_TYPE (GisAccountPageLocal, gis_account_page_local, GIS, ACCOUNT_PAGE_LOCAL, AdwBin)
+
+gboolean gis_account_page_local_validate (GisAccountPageLocal *local);
+gboolean gis_account_page_local_apply (GisAccountPageLocal *local, GisPage *page);
+gboolean gis_account_page_local_create_user (GisAccountPageLocal *local,
+ GisPage *page,
+ GError **error);
+void gis_account_page_local_shown (GisAccountPageLocal *local);
+
+G_END_DECLS
+
diff --git a/gnome-initial-setup/pages/account/gis-account-page-local.ui b/gnome-initial-setup/pages/account/gis-account-page-local.ui
new file mode 100644
index 0000000..67aaf9a
--- /dev/null
+++ b/gnome-initial-setup/pages/account/gis-account-page-local.ui
@@ -0,0 +1,145 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="GisAccountPageLocal" parent="AdwBin">
+ <child>
+ <object class="GtkBox" id="area">
+ <property name="halign">center</property>
+ <property name="valign">fill</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkMenuButton" id="avatar_button">
+ <property name="margin_top">24</property>
+ <property name="halign">center</property>
+ <style>
+ <class name="flat"/>
+ </style>
+ <accessibility>
+ <property name="description" translatable="yes">Avatar image</property>
+ </accessibility>
+ <child>
+ <object class="GtkImage" id="avatar_image">
+ <property name="pixel_size">96</property>
+ <property name="icon_name">avatar-default-symbolic</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GisPageHeader" id="header">
+ <property name="show-icon">False</property>
+ <property name="margin_top">18</property>
+ <property name="title" translatable="yes">About You</property>
+ <property name="subtitle" translatable="yes">Please provide a name and username. You can choose a picture too.</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkGrid" id="form">
+ <property name="column_spacing">12</property>
+ <property name="row_spacing">6</property>
+ <property name="margin_top">42</property>
+ <child>
+ <object class="GtkLabel" id="fullname_label">
+ <property name="halign">end</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">_Full Name</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">fullname_entry</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">3</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="fullname_entry">
+ <property name="max_length">255</property>
+ <property name="width-chars">25</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">3</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="username_label">
+ <property name="halign">end</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">_Username</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">username_combo</property>
+ <property name="margin_top">6</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">4</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="username_combo">
+ <property name="has_entry">True</property>
+ <property name="entry_text_column">0</property>
+ <property name="id_column">1</property>
+ <property name="margin_top">6</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">4</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="username_explanation">
+ <property name="yalign">0</property>
+ <property name="xalign">0</property>
+ <property name="width-chars">35</property>
+ <property name="max-width-chars">35</property>
+ <property name="height-request">50</property>
+ <property name="wrap">True</property>
+ <property name="wrap_mode">word-char</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">5</property>
+ </layout>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ <attributes>
+ <attribute name="scale" value="0.8"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="enable_parental_controls_box">
+ <property name="orientation">vertical</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">6</property>
+ </layout>
+ <child>
+ <object class="GtkCheckButton" id="enable_parental_controls_check_button">
+ <property name="label" translatable="yes">Set up _parental controls for this user</property>
+ <property name="use-underline">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">For use by a parent or supervisor, who must set up their own password.</property>
+ <property name="yalign">0</property>
+ <property name="xalign">0</property>
+ <property name="margin-start">24</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ <attributes>
+ <attribute name="scale" value="0.8"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
+
diff --git a/gnome-initial-setup/pages/account/gis-account-page.c b/gnome-initial-setup/pages/account/gis-account-page.c
new file mode 100644
index 0000000..04a8e54
--- /dev/null
+++ b/gnome-initial-setup/pages/account/gis-account-page.c
@@ -0,0 +1,309 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2012 Red Hat
+ *
+ * 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 2 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/>.
+ *
+ * Written by:
+ * Jasper St. Pierre <jstpierre@mecheye.net>
+ */
+
+/* Account page {{{1 */
+
+#define PAGE_ID "account"
+
+#include "config.h"
+#include "account-resources.h"
+#include "gis-account-page.h"
+#include "gis-account-page-local.h"
+#include "gis-account-page-enterprise.h"
+
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+
+struct _GisAccountPage
+{
+ GisPage parent;
+
+ GtkWidget *page_local;
+ GtkWidget *page_enterprise;
+ GtkWidget *stack;
+
+ GtkWidget *page_toggle;
+ GtkWidget *offline_label;
+ GtkWidget *offline_stack;
+
+ UmAccountMode mode;
+};
+
+G_DEFINE_TYPE (GisAccountPage, gis_account_page, GIS_TYPE_PAGE);
+
+static void
+enterprise_apply_complete (GisPage *dummy,
+ gboolean valid,
+ gpointer user_data)
+{
+ GisAccountPage *page = GIS_ACCOUNT_PAGE (user_data);
+ gis_driver_set_username (GIS_PAGE (page)->driver, NULL);
+ gis_page_apply_complete (GIS_PAGE (page), valid);
+}
+
+static gboolean
+page_validate (GisAccountPage *page)
+{
+ switch (page->mode) {
+ case UM_LOCAL:
+ return gis_account_page_local_validate (GIS_ACCOUNT_PAGE_LOCAL (page->page_local));
+ case UM_ENTERPRISE:
+ return gis_account_page_enterprise_validate (GIS_ACCOUNT_PAGE_ENTERPRISE (page->page_enterprise));
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+update_page_validation (GisAccountPage *page)
+{
+ gis_page_set_complete (GIS_PAGE (page), page_validate (page));
+}
+
+static void
+on_validation_changed (gpointer page_area,
+ GisAccountPage *page)
+{
+ update_page_validation (page);
+}
+
+static void
+set_mode (GisAccountPage *page,
+ UmAccountMode mode)
+{
+ if (page->mode == mode)
+ return;
+
+ page->mode = mode;
+ gis_driver_set_account_mode (GIS_PAGE (page)->driver, mode);
+
+ switch (mode)
+ {
+ case UM_LOCAL:
+ gtk_stack_set_visible_child (GTK_STACK (page->stack), page->page_local);
+ gis_account_page_local_shown (GIS_ACCOUNT_PAGE_LOCAL (page->page_local));
+ break;
+ case UM_ENTERPRISE:
+ gtk_stack_set_visible_child (GTK_STACK (page->stack), page->page_enterprise);
+ gis_account_page_enterprise_shown (GIS_ACCOUNT_PAGE_ENTERPRISE (page->page_enterprise));
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ update_page_validation (page);
+}
+
+static void
+toggle_mode (GtkToggleButton *button,
+ gpointer user_data)
+{
+ set_mode (GIS_ACCOUNT_PAGE (user_data),
+ gtk_toggle_button_get_active (button) ? UM_ENTERPRISE : UM_LOCAL);
+}
+
+static gboolean
+gis_account_page_apply (GisPage *gis_page,
+ GCancellable *cancellable)
+{
+ GisAccountPage *page = GIS_ACCOUNT_PAGE (gis_page);
+
+ switch (page->mode) {
+ case UM_LOCAL:
+ return gis_account_page_local_apply (GIS_ACCOUNT_PAGE_LOCAL (page->page_local), gis_page);
+ case UM_ENTERPRISE:
+ return gis_account_page_enterprise_apply (GIS_ACCOUNT_PAGE_ENTERPRISE (page->page_enterprise), cancellable,
+ enterprise_apply_complete, page);
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+static gboolean
+gis_account_page_save_data (GisPage *gis_page,
+ GError **error)
+{
+ GisAccountPage *page = GIS_ACCOUNT_PAGE (gis_page);
+
+ switch (page->mode) {
+ case UM_LOCAL:
+ return gis_account_page_local_create_user (GIS_ACCOUNT_PAGE_LOCAL (page->page_local), gis_page, error);
+ case UM_ENTERPRISE:
+ /* Nothing to do. */
+ return TRUE;
+ default:
+ g_assert_not_reached ();
+ return FALSE;
+ }
+}
+
+static void
+gis_account_page_shown (GisPage *gis_page)
+{
+ GisAccountPage *page = GIS_ACCOUNT_PAGE (gis_page);
+
+ gis_account_page_local_shown (GIS_ACCOUNT_PAGE_LOCAL (page->page_local));
+}
+
+static void
+on_local_main_user_created (GtkWidget *page_local,
+ ActUser *user,
+ const gchar *password,
+ GisAccountPage *page)
+{
+ const gchar *language;
+
+ language = gis_driver_get_user_language (GIS_PAGE (page)->driver);
+ if (language)
+ act_user_set_language (user, language);
+
+ gis_driver_set_user_permissions (GIS_PAGE (page)->driver, user, password);
+}
+
+static void
+on_local_parent_user_created (GtkWidget *page_local,
+ ActUser *user,
+ const gchar *password,
+ GisAccountPage *page)
+{
+ const gchar *language;
+
+ language = gis_driver_get_user_language (GIS_PAGE (page)->driver);
+ if (language)
+ act_user_set_language (user, language);
+
+ gis_driver_set_parent_permissions (GIS_PAGE (page)->driver, user, password);
+}
+
+static void
+on_local_page_confirmed (GisAccountPageLocal *local,
+ GisAccountPage *page)
+{
+ gis_assistant_next_page (gis_driver_get_assistant (GIS_PAGE (page)->driver));
+}
+
+static void
+on_local_user_cached (GtkWidget *page_local,
+ ActUser *user,
+ char *password,
+ GisAccountPage *page)
+{
+ const gchar *language;
+
+ language = gis_driver_get_user_language (GIS_PAGE (page)->driver);
+ if (language)
+ act_user_set_language (user, language);
+
+ gis_driver_set_user_permissions (GIS_PAGE (page)->driver, user, password);
+}
+
+static void
+on_network_changed (GNetworkMonitor *monitor,
+ gboolean available,
+ GisAccountPage *page)
+{
+ if (!available && page->mode != UM_ENTERPRISE)
+ gtk_stack_set_visible_child (GTK_STACK (page->offline_stack), page->offline_label);
+ else
+ gtk_stack_set_visible_child (GTK_STACK (page->offline_stack), page->page_toggle);
+}
+
+static void
+gis_account_page_constructed (GObject *object)
+{
+ GisAccountPage *page = GIS_ACCOUNT_PAGE (object);
+ GNetworkMonitor *monitor;
+ gboolean available;
+
+ G_OBJECT_CLASS (gis_account_page_parent_class)->constructed (object);
+
+ g_signal_connect (page->page_local, "validation-changed",
+ G_CALLBACK (on_validation_changed), page);
+ g_signal_connect (page->page_local, "main-user-created",
+ G_CALLBACK (on_local_main_user_created), page);
+ g_signal_connect (page->page_local, "parent-user-created",
+ G_CALLBACK (on_local_parent_user_created), page);
+ g_signal_connect (page->page_local, "confirm",
+ G_CALLBACK (on_local_page_confirmed), page);
+
+ g_signal_connect (page->page_enterprise, "validation-changed",
+ G_CALLBACK (on_validation_changed), page);
+ g_signal_connect (page->page_enterprise, "user-cached",
+ G_CALLBACK (on_local_user_cached), page);
+
+ update_page_validation (page);
+
+ g_signal_connect (page->page_toggle, "toggled", G_CALLBACK (toggle_mode), page);
+ g_object_bind_property (page, "applying", page->page_toggle, "sensitive", G_BINDING_INVERT_BOOLEAN);
+ g_object_bind_property (page->page_enterprise, "visible", page->offline_stack, "visible", G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
+
+ /* force a refresh by setting to an invalid value */
+ page->mode = NUM_MODES;
+ set_mode (page, UM_LOCAL);
+
+ monitor = g_network_monitor_get_default ();
+ available = g_network_monitor_get_network_available (monitor);
+ on_network_changed (monitor, available, page);
+ g_signal_connect_object (monitor, "network-changed", G_CALLBACK (on_network_changed), page, 0);
+
+ gtk_widget_show (GTK_WIDGET (page));
+}
+
+static void
+gis_account_page_locale_changed (GisPage *page)
+{
+ gis_page_set_title (GIS_PAGE (page), _("About You"));
+}
+
+static void
+gis_account_page_class_init (GisAccountPageClass *klass)
+{
+ GisPageClass *page_class = GIS_PAGE_CLASS (klass);
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (klass), "/org/gnome/initial-setup/gis-account-page.ui");
+
+ gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPage, page_local);
+ gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPage, page_enterprise);
+ gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPage, stack);
+
+ gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPage, page_toggle);
+ gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPage, offline_label);
+ gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPage, offline_stack);
+
+ page_class->page_id = PAGE_ID;
+ page_class->locale_changed = gis_account_page_locale_changed;
+ page_class->apply = gis_account_page_apply;
+ page_class->save_data = gis_account_page_save_data;
+ page_class->shown = gis_account_page_shown;
+ object_class->constructed = gis_account_page_constructed;
+}
+
+static void
+gis_account_page_init (GisAccountPage *page)
+{
+ g_resources_register (account_get_resource ());
+ g_type_ensure (GIS_TYPE_ACCOUNT_PAGE_LOCAL);
+ g_type_ensure (GIS_TYPE_ACCOUNT_PAGE_ENTERPRISE);
+
+ gtk_widget_init_template (GTK_WIDGET (page));
+}
diff --git a/gnome-initial-setup/pages/account/gis-account-page.h b/gnome-initial-setup/pages/account/gis-account-page.h
new file mode 100644
index 0000000..17a86fa
--- /dev/null
+++ b/gnome-initial-setup/pages/account/gis-account-page.h
@@ -0,0 +1,33 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2012 Red Hat
+ *
+ * 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 2 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/>.
+ *
+ * Written by:
+ * Jasper St. Pierre <jstpierre@mecheye.net>
+ */
+
+#pragma once
+
+#include <glib-object.h>
+
+#include "gnome-initial-setup.h"
+
+G_BEGIN_DECLS
+
+#define GIS_TYPE_ACCOUNT_PAGE (gis_account_page_get_type ())
+G_DECLARE_FINAL_TYPE (GisAccountPage, gis_account_page, GIS, ACCOUNT_PAGE, GisPage)
+
+G_END_DECLS
diff --git a/gnome-initial-setup/pages/account/gis-account-page.ui b/gnome-initial-setup/pages/account/gis-account-page.ui
new file mode 100644
index 0000000..782e4bd
--- /dev/null
+++ b/gnome-initial-setup/pages/account/gis-account-page.ui
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="GisAccountPage" parent="GisPage">
+ <child>
+ <object class="AdwPreferencesPage">
+
+ <child>
+ <object class="AdwPreferencesGroup">
+ <child>
+ <object class="GtkStack" id="stack">
+ <property name="valign">start</property>
+ <property name="vexpand">True</property>
+ <child>
+ <object class="GisAccountPageLocal" id="page_local" />
+ </child>
+ <child>
+ <object class="GisAccountPageEnterprise" id="page_enterprise" />
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <child>
+ <object class="AdwPreferencesGroup">
+ <child>
+ <object class="GtkStack" id="offline_stack">
+ <property name="valign">end</property>
+ <child>
+ <object class="GtkToggleButton" id="page_toggle">
+ <property name="use_underline">True</property>
+ <property name="label" translatable="yes">_Enterprise Login</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="offline_label">
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="label" translatable="yes">Go online to set up Enterprise Login.</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ </object>
+ </child>
+
+ </template>
+</interface>
diff --git a/gnome-initial-setup/pages/account/gis-account-pages.c b/gnome-initial-setup/pages/account/gis-account-pages.c
new file mode 100644
index 0000000..d9cc8d9
--- /dev/null
+++ b/gnome-initial-setup/pages/account/gis-account-pages.c
@@ -0,0 +1,32 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2013 Red Hat
+ *
+ * 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 2 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/>.
+ *
+ * Written by:
+ * Jasper St. Pierre <jstpierre@mecheye.net>
+ */
+
+#include "config.h"
+#include "gis-account-pages.h"
+#include "gis-account-page.h"
+
+GisPage *
+gis_prepare_account_page (GisDriver *driver)
+{
+ return g_object_new (GIS_TYPE_ACCOUNT_PAGE,
+ "driver", driver,
+ NULL);
+}
diff --git a/gnome-initial-setup/pages/account/gis-account-pages.h b/gnome-initial-setup/pages/account/gis-account-pages.h
new file mode 100644
index 0000000..9cf41bb
--- /dev/null
+++ b/gnome-initial-setup/pages/account/gis-account-pages.h
@@ -0,0 +1,33 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2013 Red Hat
+ *
+ * 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 2 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/>.
+ *
+ * Written by:
+ * Jasper St. Pierre <jstpierre@mecheye.net>
+ */
+
+#pragma once
+
+#include <glib-object.h>
+
+#include "gnome-initial-setup.h"
+
+G_BEGIN_DECLS
+
+GisPage *gis_prepare_account_page (GisDriver *driver);
+
+G_END_DECLS
+
diff --git a/gnome-initial-setup/pages/account/meson.build b/gnome-initial-setup/pages/account/meson.build
new file mode 100644
index 0000000..1130465
--- /dev/null
+++ b/gnome-initial-setup/pages/account/meson.build
@@ -0,0 +1,33 @@
+realmd_namespace = 'org.freedesktop.realmd'
+sources += gnome.gdbus_codegen(
+ 'um-realm-generated',
+ realmd_namespace + '.xml',
+ interface_prefix: realmd_namespace + '.',
+ namespace: 'UmRealm',
+ object_manager: true,
+ annotations: ['org.freedesktop.realmd.Realm', 'org.gtk.GDBus.C.Name', 'Common']
+)
+
+sources += gnome.compile_resources(
+ 'account-resources',
+ files('account.gresource.xml'),
+ c_name: 'account'
+)
+
+sources += files(
+ 'gis-account-page.c',
+ 'gis-account-page.h',
+ 'gis-account-pages.c',
+ 'gis-account-pages.h',
+ 'gis-account-page-local.c',
+ 'gis-account-page-local.h',
+ 'gis-account-page-enterprise.c',
+ 'gis-account-page-enterprise.h',
+ 'um-realm-manager.c',
+ 'um-realm-manager.h',
+ 'um-utils.c',
+ 'um-photo-dialog.c',
+ 'um-photo-dialog.h'
+)
+
+account_sources_dir = meson.current_source_dir()
diff --git a/gnome-initial-setup/pages/account/org.freedesktop.realmd.xml b/gnome-initial-setup/pages/account/org.freedesktop.realmd.xml
new file mode 100644
index 0000000..316213a
--- /dev/null
+++ b/gnome-initial-setup/pages/account/org.freedesktop.realmd.xml
@@ -0,0 +1,666 @@
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node name="/">
+
+ <!--
+ org.freedesktop.realmd.Provider:
+ @short_description: a realm provider
+
+ Various realm providers represent different software implementations
+ that provide access to realms or domains.
+
+ This interface is implemented by individual providers, but is
+ aggregated globally at the system bus name
+ <literal>org.freedesktop.realmd</literal>
+ with the object path <literal>/org/freedesktop/realmd</literal>
+ -->
+ <interface name="org.freedesktop.realmd.Provider">
+
+ <!--
+ Name: the name of the provider
+
+ The name of the provider. This is not normally displayed
+ to the user, but may be useful for diagnostics or debugging.
+ -->
+ <property name="Name" type="s" access="read"/>
+
+ <!--
+ Version: the version of the provider
+
+ The version of the provider. This is not normally used in
+ logic, but may be useful for diagnostics or debugging.
+ -->
+ <property name="Version" type="s" access="read"/>
+
+ <!--
+ Realms: a list of realms
+
+ A list of known, enrolled or discovered realms. All realms
+ that this provider knows about are listed here. As realms
+ are discovered they are added to this list.
+
+ Each realm is represented by the DBus object path of the
+ realm object.
+ -->
+ <property name="Realms" type="ao" access="read"/>
+
+ <!--
+ Discover:
+ @string: an input string to discover realms for
+ @options: options for the discovery operation
+ @relevance: the relevance of the returned results
+ @realm: a list of realms discovered
+
+ Discover realms for the given string. The input @string is
+ usually a domain or realm name, perhaps typed by a user. If
+ an empty string is provided the realm provider should try to
+ discover a default realm if possible (eg: from DHCP).
+
+ @options can contain, but is not limited to, the following values:
+ <itemizedlist>
+ <listitem><para><literal>operation</literal>: a string
+ identifier chosen by the client, which can then later be
+ passed to org.freedesktop.realmd.Service.Cancel() in order
+ to cancel the operation</para></listitem>
+ </itemizedlist>
+
+ The @relevance returned can be used to rank results from
+ different discover calls to different providers. Implementors
+ should return a positive number if the provider highly
+ recommends that the realms be handled by this provider,
+ or a zero if it can possibly handle the realms. Negative
+ should be returned if no realms are found.
+
+ This method does not return an error when no realms are
+ discovered. It simply returns an @realm list.
+
+ To see diagnostic information about the discovery process
+ connect to the org.freedesktop.realmd.Service::Diagnostics
+ signal.
+
+ This method requires authorization for the PolicyKit action
+ called <literal>org.freedesktop.realmd.discover-realm</literal>.
+
+ In addition to common DBus error results, this method may
+ return:
+ <itemizedlist>
+ <listitem><para><literal>org.freedesktop.realmd.Error.Failed</literal>:
+ may be returned if the discovery could not be run for some reason.</para></listitem>
+ <listitem><para><literal>org.freedesktop.realmd.Error.Cancelled</literal>:
+ returned if the operation was cancelled.</para></listitem>
+ <listitem><para><literal>org.freedesktop.realmd.Error.NotAuthorized</literal>:
+ returned if the calling client is not permitted to perform a discovery
+ operation.</para></listitem>
+ </itemizedlist>
+ -->
+ <method name="Discover">
+ <arg name="string" type="s" direction="in"/>
+ <arg name="options" type="a{sv}" direction="in"/>
+ <arg name="relevance" type="i" direction="out"/>
+ <arg name="realm" type="ao" direction="out"/>
+ </method>
+
+ </interface>
+
+ <!--
+ org.freedesktop.realmd.Service:
+ @short_description: the realmd service
+
+ Global calls for managing the realmd service. Usually you'll want
+ to use #org.freedesktop.realmd.Provider instead.
+
+ This interface is implemented by the realmd service, and is always
+ available at the object path <literal>/org/freedesktop/realmd</literal>
+
+ The service also implements the
+ <literal>org.freedesktop.DBus.ObjectManager</literal> interface which
+ makes it easy to retrieve all realmd objects and properties in one go.
+ -->
+ <interface name="org.freedesktop.realmd.Service">
+
+ <!--
+ Cancel:
+ @operation: the operation to cancel
+
+ Cancel a realmd operation. To be able to cancel an operation
+ pass a uniquely chosen <literal>operation</literal> string
+ identifier as an option in the methods <literal>options</literal>
+ argument.
+
+ These operation string identifiers should be unique per client
+ calling the realmd service.
+
+ It is not guaranteed that the service can or will cancel the
+ operation. For example the operation may have already completed
+ by the time this method is handled. The caller of the operation
+ method will receive a
+ <literal>org.freedesktop.realmd.Error.Cancelled</literal>
+ if the operation was cancelled.
+ -->
+ <method name="Cancel">
+ <arg name="operation" type="s" direction="in"/>
+ </method>
+
+ <!--
+ SetLocale:
+ @locale: the locale for the client
+
+ Set the language @locale for the client. This locale is used
+ for error messages. The locale is used until the next time
+ this method is called, the client disconnects, or the client
+ calls #org.freedesktop.realmd.Service.Release().
+ -->
+ <method name="SetLocale">
+ <arg name="locale" type="s" direction="in"/>
+ </method>
+
+ <!--
+ Diagnostics:
+ @data: diagnostic data
+ @operation: the operation this data resulted from
+
+ This signal is fired when diagnostics result from an operation
+ in the provider or one of its realms.
+
+ It is not guaranteed that this signal is emitted once per line.
+ More than one line may be contained in @data, or a partial
+ line. New line characters are embedded in @data.
+
+ This signal is sent explicitly to the client which invoked
+ operation method. In order to tell which operation this
+ diagnostic data results from, pass a unique
+ <literal>operation</literal> string identifier in the
+ <literal>options</literal> argument of the operation method.
+ That same identifier will be passed back via the @operation
+ argument of this signal.
+ -->
+ <signal name="Diagnostics">
+ <arg name="data" type="s"/>
+ <arg name="operation" type="s"/>
+ </signal>
+
+ <!--
+ Release:
+
+ Normally realmd waits until all clients have disconnected
+ before exiting itself, sometime later. For long lived clients
+ they can call this method to allow the realmd service to quit.
+ This is an optimization. The daemon will not exit immediately.
+ It is safe to call this multiple times.
+ -->
+ <method name="Release">
+ <!-- no arguments -->
+ </method>
+
+ </interface>
+
+ <!--
+ org.freedesktop.realmd.Realm:
+ @short_description: a realm
+
+ Represents one realm.
+
+ Contains generic information about a realm, and useful properties for
+ introspecting what kind of realm this is and how to work with
+ the realm.
+
+ Use #org.freedesktop.realmd.Provider:Realms or
+ #org.freedesktop.realmd.Provider.Discover() to get access to some
+ kerberos realm objects.
+
+ Realms will always implement additional interfaces, such as
+ #org.freedesktop.realmd.Kerberos. Do not assume that all realms
+ implement that kerberos interface. Use the
+ #org.freedesktop.realmd.Realm:SupportedInterfaces property to see
+ which interfaces are set.
+
+ Different realms support various ways to configure them on the
+ system. Use the #org.freedesktop.realmd.Realm:Configured property
+ to determine if a realm is configured. If it is configured the
+ property will be set to the interface of the mechanism that was
+ used to configure it.
+
+ To configure a realm, look in the
+ #org.freedesktop.realmd.Realm:SupportedInterfaces property for a
+ recognized purpose specific interface that can be used for
+ configuration, such as the
+ #org.freedesktop.realmd.KerberosMembership interface and its
+ #org.freedesktop.realmd.KerberosMembership.Join() method.
+
+ To deconfigure a realm from the current system, you can use the
+ #org.freedesktop.realmd.Realm.Deconfigure() method. In additon some
+ of the configuration specific interfaces provide methods to
+ deconfigure a realm in a specific way, such as
+ #org.freedesktop.realmd.KerberosMembership.Leave() method.
+
+ The various properties are guaranteed to have been updated before
+ the operation methods return, if they change state.
+ -->
+ <interface name="org.freedesktop.realmd.Realm">
+
+ <!--
+ Name: the realm name
+
+ This is the name of the realm, appropriate for display to
+ end users where necessary.
+ -->
+ <property name="Name" type="s" access="read"/>
+
+ <!--
+ Configured: whether this domain is configured and how
+
+ If this property is an empty string, then the realm is not
+ configured. Otherwise the realm is configured, and contains
+ a string which is the interface that represents how it was
+ configured, for example #org.freedesktop.realmd.KerberosMembership.
+ -->
+ <property name="Configured" type="s" access="read"/>
+
+ <!--
+ Deconfigure: deconfigure this realm
+
+ Deconfigure this realm from the local machine with standard
+ default behavior.
+
+ The behavior of this method depends on the which configuration
+ interface is present in the
+ #org.freedesktop.realmd.Realm.Configured property. It does not
+ always delete membership accounts in the realm, but just
+ reconfigures the local machine so it no longer is configured
+ for the given realm. In some cases the implementation may try
+ to update membership accounts, but this is not guaranteed.
+
+ Various configuration interfaces may support more specific ways
+ to deconfigure a realm in a specific way, such as the
+ #org.freedesktop.realmd.KerberosMembership.Leave() method.
+
+ @options can contain, but is not limited to, the following values:
+ <itemizedlist>
+ <listitem><para><literal>operation</literal>: a string
+ identifier chosen by the client, which can then later be
+ passed to org.freedesktop.realmd.Service.Cancel() in order
+ to cancel the operation</para></listitem>
+ </itemizedlist>
+
+ This method requires authorization for the PolicyKit action
+ called <literal>org.freedesktop.realmd.deconfigure-realm</literal>.
+
+ In addition to common DBus error results, this method may return:
+ <itemizedlist>
+ <listitem><para><literal>org.freedesktop.realmd.Error.Failed</literal>:
+ may be returned if the deconfigure failed for a generic reason.</para></listitem>
+ <listitem><para><literal>org.freedesktop.realmd.Error.Cancelled</literal>:
+ returned if the operation was cancelled.</para></listitem>
+ <listitem><para><literal>org.freedesktop.realmd.Error.NotAuthorized</literal>:
+ returned if the calling client is not permitted to deconfigure a
+ realm.</para></listitem>
+ <listitem><para><literal>org.freedesktop.realmd.Error.NotConfigured</literal>:
+ returned if this realm is not configured on the machine.</para></listitem>
+ <listitem><para><literal>org.freedesktop.realmd.Error.Busy</literal>:
+ returned if the service is currently performing another operation like
+ join or leave.</para></listitem>
+ </itemizedlist>
+ -->
+ <method name="Deconfigure">
+ <arg name="options" type="a{sv}" direction="in"/>
+ </method>
+
+ <!--
+ SupportedInterfaces:
+
+ Additional supported interfaces of this realm. This includes
+ interfaces that contain more information about the realm,
+ such as #org.freedesktop.realmd.Kerberos and interfaces
+ which contain methods for configuring a realm, such as
+ #org.freedesktop.realmd.KerberosMembership.
+ -->
+ <property name="SupportedInterfaces" type="as" access="read"/>
+
+ <!--
+ Details: informational details about the realm
+
+ Informational details about the realm. The following values
+ should be present:
+ <itemizedlist>
+ <listitem><para><literal>server-software</literal>:
+ identifier of the software running on the server (eg:
+ <literal>active-directory</literal>).</para></listitem>
+ <listitem><para><literal>client-software</literal>:
+ identifier of the software running on the client (eg:
+ <literal>sssd</literal>).</para></listitem>
+ </itemizedlist>
+ -->
+ <property name="Details" type="a(ss)" access="read"/>
+
+ <!--
+ LoginFormats: supported formats for login names
+
+ Supported formats for login to this realm. This is only
+ relevant once the realm has been enrolled. The formats
+ will contain a <literal>%U</literal> in the string, which
+ indicate where the user name should be placed. The formats
+ may contain a <literal>%D</literal> in the string which
+ indicate where a domain name should be placed.
+
+ The first format in the list is the preferred format for
+ login names.
+ -->
+ <property name="LoginFormats" type="as" access="read"/>
+
+ <!--
+ LoginPolicy: the policy for logins using this realm
+
+ The policy for logging into this computer using this realm.
+
+ The policy can be changed using the
+ #org.freedesktop.realmd.Realm.ChangeLoginPolicy() method.
+
+ The following policies are predefined. Not all providers
+ support all these policies and there may be provider specific
+ policies or multiple policies represented in the string:
+ <itemizedlist>
+ <listitem><para><literal>allow-any-login</literal>: allow
+ login by any authenticated user present in this
+ realm.</para></listitem>
+ <listitem><para><literal>allow-permitted-logins</literal>:
+ only allow the logins permitted in the
+ #org.freedesktop.realmd.Realm:PermittedLogins
+ property.</para></listitem>
+ <listitem><para><literal>deny-any-login</literal>:
+ don't allow any logins via authenticated users of this
+ realm.</para></listitem>
+ </itemizedlist>
+ -->
+ <property name="LoginPolicy" type="s" access="read"/>
+
+ <!--
+ PermittedLogins: the permitted login names
+
+ The list of permitted authenticated users allowed to login
+ into this computer. This is only relevant if the
+ #org.freedesktop.realmd.Realm:LoginPolicy property
+ contains the <literal>allow-permitted-logins</literal>
+ string.
+ -->
+ <property name="PermittedLogins" type="as" access="read"/>
+
+ <!--
+ ChangeLoginPolicy:
+ @login_policy: the new login policy, or an empty string
+ @permitted_add: a list of logins to permit
+ @permitted_remove: a list of logins to not permit
+ @options: options for this operation
+
+ Change the login policy and/or permitted logins for this realm.
+
+ Not all realms support the all the various login policies. An
+ error will be returned if the new login policy is not supported.
+ You may specify an empty string for the @login_policy argument
+ which will cause no change in the policy itself. If the policy
+ is changed, it will be reflected in the
+ #org.freedesktop.realmd.Realm:LoginPolicy property.
+
+ The @permitted_add and @permitted_remove arguments represent
+ lists of login names that should be added and removed from
+ the #org.freedesktop.realmd.Kerberos:PermittedLogins property.
+
+ @options can contain, but is not limited to, the following values:
+ <itemizedlist>
+ <listitem><para><literal>operation</literal>: a string
+ identifier chosen by the client, which can then later be
+ passed to org.freedesktop.realmd.Service.Cancel() in order
+ to cancel the operation</para></listitem>
+ </itemizedlist>
+
+ This method requires authorization for the PolicyKit action
+ called <literal>org.freedesktop.realmd.login-policy</literal>.
+
+ In addition to common DBus error results, this method may return:
+ <itemizedlist>
+ <listitem><para><literal>org.freedesktop.realmd.Error.Failed</literal>:
+ may be returned if the policy change failed for a generic reason.</para></listitem>
+ <listitem><para><literal>org.freedesktop.realmd.Error.Cancelled</literal>:
+ returned if the operation was cancelled.</para></listitem>
+ <listitem><para><literal>org.freedesktop.realmd.Error.NotAuthorized</literal>:
+ returned if the calling client is not permitted to change login policy
+ operation.</para></listitem>
+ <listitem><para><literal>org.freedesktop.realmd.Error.NotConfigured</literal>:
+ returned if the realm is not configured.</para></listitem>
+ <listitem><para><literal>org.freedesktop.realmd.Error.Busy</literal>:
+ returned if the service is currently performing another operation like
+ join or leave.</para></listitem>
+ </itemizedlist>
+ -->
+ <method name="ChangeLoginPolicy">
+ <arg name="login_policy" type="s" direction="in"/>
+ <arg name="permitted_add" type="as" direction="in"/>
+ <arg name="permitted_remove" type="as" direction="in"/>
+ <arg name="options" type="a{sv}" direction="in"/>
+ </method>
+
+ </interface>
+
+ <!--
+ org.freedesktop.realmd.Kerberos:
+ @short_description: a kerberos realm
+
+ An interface that describes a kerberos realm in more detail. This
+ is always implemented on an DBus object path that also implements
+ the #org.freedesktop.realmd.Realm interface.
+ -->
+ <interface name="org.freedesktop.realmd.Kerberos">
+
+ <!--
+ RealmName: the kerberos realm name
+
+ The kerberos name for this realm. This is usually in upper
+ case.
+ -->
+ <property name="RealmName" type="s" access="read"/>
+
+ <!--
+ DomainName: the DNS domain name
+
+ The DNS domain name for this realm.
+ -->
+ <property name="DomainName" type="s" access="read"/>
+
+ </interface>
+
+ <!--
+ org.freedesktop.realmd.KerberosMembership:
+
+ An interface used to configure this machine by joining a realm.
+
+ It sets up a computer/host account in the realm for this machine
+ and a keytab to track the credentials for that account.
+
+ The various properties are guaranteed to have been updated before
+ the operation methods return, if they change state.
+ -->
+ <interface name="org.freedesktop.realmd.KerberosMembership">
+
+ <!--
+ SuggestedAdministrator: common administrator name
+
+ The common administrator name for this type of realm. This
+ can be used by clients as a hint when prompting the user for
+ administrative authentication.
+ -->
+ <property name="SuggestedAdministrator" type="s" access="read"/>
+
+ <!--
+ SupportedJoinCredentials: credentials supported for joining
+
+ Various kinds of credentials that are supported when calling the
+ #org.freedesktop.realmd.Kerberos.Join() method.
+
+ Each credential is represented by a type, and an owner. The type
+ denotes which kind of credential is passed to the method. The
+ owner indicates to the client how to prompt the user or obtain
+ the credential, and to the service how to use the credential.
+
+ The various types are:
+ <itemizedlist>
+ <listitem><para><literal>ccache</literal>:
+ the credentials should contain an array of bytes as a
+ <literal>ay</literal> containing the data from a kerberos
+ credential cache file.</para></listitem>
+ <listitem><para><literal>password</literal>:
+ the credentials should contain a pair of strings as a
+ <literal>(ss)</literal> representing a name and
+ password. The name may contain a realm in the standard
+ kerberos format. If missing, it will default to this
+ realm. The name may be empty for a computer or one time
+ password.</para></listitem>
+ <listitem><para><literal>automatic</literal>:
+ the credentials should contain an empty string as a
+ <literal>s</literal>. Using <literal>automatic</literal>
+ indicates that default or system credentials are to be
+ used.</para></listitem>
+ </itemizedlist>
+
+ The various owners are:
+ <itemizedlist>
+ <listitem><para><literal>administrator</literal>:
+ the credentials belong to a kerberos user principal.
+ The caller may use this as a hint to prompt the user
+ for administrative credentials.</para></listitem>
+ <listitem><para><literal>user</literal>:
+ the credentials belong to a kerberos user principal.
+ The caller may use this as a hint to prompt the user
+ for his (possibly non-administrative)
+ credentials.</para></listitem>
+ <listitem><para><literal>computer</literal>:
+ the credentials belong to the computer realmd is
+ being run on.</para></listitem>
+ <listitem><para><literal>secret</literal>:
+ the credentials are a one time password or other secret
+ used to join or leave the computer.</para></listitem>
+ </itemizedlist>
+ -->
+ <property name="SupportedJoinCredentials" type="a(ss)" access="read"/>
+
+ <!--
+ SupportedLeaveCredentials: credentials supported for leaving
+
+ Various kinds of credentials that are supported when calling the
+ #org.freedesktop.realmd.Kerberos.Leave() method.
+
+ See #org.freedesktop.realmd.Kerberos:SupportedJoinCredentials for
+ a discussion of what the values represent.
+ -->
+ <property name="SupportedLeaveCredentials" type="a(ss)" access="read"/>
+
+ <!--
+ Join:
+
+ Join this machine to the realm and enroll the machine.
+
+ If this method returns successfully then the machine will be
+ joined to the realm. It is not necessary to restart services or the
+ machine afterward. Relevant properties on the realm will be updated
+ before the method returns.
+
+ The @credentials should be set according to one of the
+ supported credentials returned by
+ #org.freedesktop.realmd.Kerberos:SupportedJoinCredentials.
+ The first string in the tuple is the type, the second string
+ is the owner, and the variant contains the credential contents
+ See the discussion at
+ #org.freedesktop.realmd.Kerberos:SupportedJoinCredentials
+ for more information.
+
+ @options can contain, but is not limited to, the following values:
+ <itemizedlist>
+ <listitem><para><literal>operation</literal>: a string
+ identifier chosen by the client, which can then later be
+ passed to org.freedesktop.realmd.Service.Cancel() in order
+ to cancel the operation</para></listitem>
+ </itemizedlist>
+
+ This method requires authorization for the PolicyKit action
+ called <literal>org.freedesktop.realmd.configure-realm</literal>.
+
+ In addition to common DBus error results, this method may return:
+ <itemizedlist>
+ <listitem><para><literal>org.freedesktop.realmd.Error.Failed</literal>:
+ may be returned if the join failed for a generic reason.</para></listitem>
+ <listitem><para><literal>org.freedesktop.realmd.Error.Cancelled</literal>:
+ returned if the operation was cancelled.</para></listitem>
+ <listitem><para><literal>org.freedesktop.realmd.Error.NotAuthorized</literal>:
+ returned if the calling client is not permitted to perform an join
+ operation.</para></listitem>
+ <listitem><para><literal>org.freedesktop.realmd.Error.AuthenicationFailed</literal>:
+ returned if the credentials passed did not authenticate against the realm
+ correctly. It is appropriate to prompt the user again.</para></listitem>
+ <listitem><para><literal>org.freedesktop.realmd.Error.AlreadyEnrolled</literal>:
+ returned if already enrolled in this realm, or another realm and enrolling
+ in multiple realms is not supported.</para></listitem>
+ <listitem><para><literal>org.freedesktop.realmd.Error.Busy</literal>:
+ returned if the service is currently performing another operation like
+ join or leave.</para></listitem>
+ </itemizedlist>
+ -->
+ <method name="Join">
+ <arg name="credentials" type="(ssv)" direction="in"/>
+ <arg name="options" type="a{sv}" direction="in"/>
+ </method>
+
+ <!--
+ Leave:
+
+ Leave the realm and unenroll the machine.
+
+ If this method returns successfully then the machine will have
+ left the domain and been unenrolled. It is not necessary to restart
+ services or the machine afterward. Relevant properties on the realm
+ will be updated before the method returns.
+
+ The @credentials should be set according to one of the
+ supported credentials returned by
+ #org.freedesktop.realmd.Kerberos:SupportedUnenrollCredentials.
+ The first string in the tuple is the type, the second string
+ is the owner, and the variant contains the credential contents
+ See the discussion at
+ #org.freedesktop.realmd.Kerberos:SupportedEnrollCredentials
+ for more information.
+
+ @options can contain, but is not limited to, the following values:
+ <itemizedlist>
+ <listitem><para><literal>operation</literal>: a string
+ identifier chosen by the client, which can then later be
+ passed to org.freedesktop.realmd.Service.Cancel() in order
+ to cancel the operation</para></listitem>
+ </itemizedlist>
+
+ This method requires authorization for the PolicyKit action
+ called <literal>org.freedesktop.realmd.deconfigure-realm</literal>.
+
+ In addition to common DBus error results, this method may return:
+ <itemizedlist>
+ <listitem><para><literal>org.freedesktop.realmd.Error.Failed</literal>:
+ may be returned if the unenroll failed for a generic reason.</para></listitem>
+ <listitem><para><literal>org.freedesktop.realmd.Error.Cancelled</literal>:
+ returned if the operation was cancelled.</para></listitem>
+ <listitem><para><literal>org.freedesktop.realmd.Error.NotAuthorized</literal>:
+ returned if the calling client is not permitted to perform an unenroll
+ operation.</para></listitem>
+ <listitem><para><literal>org.freedesktop.realmd.Error.AuthenicationFailed</literal>:
+ returned if the credentials passed did not authenticate against the realm
+ correctly. It is appropriate to prompt the user again.</para></listitem>
+ <listitem><para><literal>org.freedesktop.realmd.Error.NotEnrolled</literal>:
+ returned if not enrolled in this realm.</para></listitem>
+ <listitem><para><literal>org.freedesktop.realmd.Error.Busy</literal>:
+ returned if the service is currently performing another operation like
+ enroll or unenroll.</para></listitem>
+ </itemizedlist>
+ -->
+ <method name="Leave">
+ <arg name="credentials" type="(ssv)" direction="in"/>
+ <arg name="options" type="a{sv}" direction="in"/>
+ </method>
+
+ </interface>
+
+</node>
diff --git a/gnome-initial-setup/pages/account/um-photo-dialog.c b/gnome-initial-setup/pages/account/um-photo-dialog.c
new file mode 100644
index 0000000..383101d
--- /dev/null
+++ b/gnome-initial-setup/pages/account/um-photo-dialog.c
@@ -0,0 +1,314 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2009-2010 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA
+ *
+ * Written by: Matthias Clasen <mclasen@redhat.com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "um-photo-dialog.h"
+#include "um-utils.h"
+
+#define ROW_SPAN 5
+#define AVATAR_PIXEL_SIZE 72
+
+struct _UmPhotoDialog {
+ GtkPopover parent;
+
+ GtkWidget *take_picture_button;
+ GtkWidget *flowbox;
+ GtkWidget *recent_pictures;
+
+ GListStore *recent_faces;
+ GListStore *faces;
+ GFile *generated_avatar;
+ gboolean custom_avatar_was_chosen;
+
+ SelectAvatarCallback *callback;
+ gpointer data;
+};
+
+G_DEFINE_TYPE (UmPhotoDialog, um_photo_dialog, GTK_TYPE_POPOVER)
+
+
+static void
+webcam_icon_selected (UmPhotoDialog *um)
+{
+ g_warning ("Webcam icon selected, but compiled without Cheese support");
+}
+
+static void
+face_widget_activated (GtkFlowBox *flowbox,
+ GtkFlowBoxChild *child,
+ UmPhotoDialog *um)
+{
+ const char *filename;
+ GtkWidget *image;
+
+ image = gtk_flow_box_child_get_child (child);
+ filename = g_object_get_data (G_OBJECT (image), "filename");
+
+ um->callback (NULL, filename, um->data);
+ um->custom_avatar_was_chosen = TRUE;
+
+ gtk_popover_popdown (GTK_POPOVER (um));
+}
+
+static void
+generated_avatar_activated (GtkFlowBox *flowbox,
+ GtkFlowBoxChild *child,
+ UmPhotoDialog *um)
+{
+ face_widget_activated (flowbox, child, um);
+ um->custom_avatar_was_chosen = FALSE;
+}
+
+static GtkWidget *
+create_face_widget (gpointer item,
+ gpointer user_data)
+{
+ g_autoptr(GdkPixbuf) pixbuf = NULL;
+ GtkWidget *image;
+ g_autofree gchar *path = g_file_get_path (G_FILE (item));
+
+ pixbuf = gdk_pixbuf_new_from_file_at_size (path,
+ AVATAR_PIXEL_SIZE,
+ AVATAR_PIXEL_SIZE,
+ NULL);
+
+ if (pixbuf != NULL)
+ image = gtk_image_new_from_pixbuf (round_image (pixbuf));
+ else
+ image = gtk_image_new ();
+
+ gtk_image_set_pixel_size (GTK_IMAGE (image), AVATAR_PIXEL_SIZE);
+
+ gtk_widget_show (image);
+
+ g_object_set_data_full (G_OBJECT (image),
+ "filename", g_steal_pointer (&path),
+ (GDestroyNotify) g_free);
+
+ return image;
+}
+
+static GStrv
+get_settings_facesdirs (void)
+{
+ g_autoptr(GSettingsSchema) schema = NULL;
+ g_autoptr(GPtrArray) facesdirs = g_ptr_array_new ();
+ g_autoptr(GSettings) settings = g_settings_new ("org.gnome.desktop.interface");
+ g_auto(GStrv) settings_dirs = g_settings_get_strv (settings, "avatar-directories");
+
+ if (settings_dirs != NULL) {
+ int i;
+ for (i = 0; settings_dirs[i] != NULL; i++) {
+ char *path = settings_dirs[i];
+ if (path != NULL && g_strcmp0 (path, "") != 0)
+ g_ptr_array_add (facesdirs, g_strdup (path));
+ }
+ }
+
+ // NULL terminated array
+ g_ptr_array_add (facesdirs, NULL);
+ return (GStrv) g_steal_pointer (&facesdirs->pdata);
+}
+
+static GStrv
+get_system_facesdirs (void)
+{
+ g_autoptr(GPtrArray) facesdirs = NULL;
+ const char * const * data_dirs;
+ int i;
+
+ facesdirs = g_ptr_array_new ();
+
+ data_dirs = g_get_system_data_dirs ();
+ for (i = 0; data_dirs[i] != NULL; i++) {
+ char *path = g_build_filename (data_dirs[i], "pixmaps", "faces", NULL);
+ g_ptr_array_add (facesdirs, path);
+ }
+
+ // NULL terminated array
+ g_ptr_array_add (facesdirs, NULL);
+ return (GStrv) g_steal_pointer (&facesdirs->pdata);
+}
+
+static gboolean
+add_faces_from_dirs (GListStore *faces, GStrv facesdirs, gboolean add_all)
+{
+ gboolean added_faces = FALSE;
+ const gchar *target;
+ int i;
+ GFileType type;
+
+ for (i = 0; facesdirs[i] != NULL; i++) {
+ g_autoptr(GFileEnumerator) enumerator = NULL;
+ g_autoptr(GFile) dir = NULL;
+ const char *path = facesdirs[i];
+ gpointer infoptr;
+
+ dir = g_file_new_for_path (path);
+ enumerator = g_file_enumerate_children (dir,
+ G_FILE_ATTRIBUTE_STANDARD_NAME ","
+ G_FILE_ATTRIBUTE_STANDARD_TYPE ","
+ G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK ","
+ G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET,
+ G_FILE_QUERY_INFO_NONE,
+ NULL, NULL);
+
+ if (enumerator == NULL)
+ continue;
+
+ while ((infoptr = g_file_enumerator_next_file (enumerator, NULL, NULL)) != NULL) {
+ g_autoptr (GFileInfo) info = infoptr;
+ g_autoptr (GFile) face_file = NULL;
+
+ type = g_file_info_get_file_type (info);
+ if (type != G_FILE_TYPE_REGULAR && type != G_FILE_TYPE_SYMBOLIC_LINK)
+ continue;
+
+ target = g_file_info_get_symlink_target (info);
+ if (target != NULL && g_str_has_prefix (target , "legacy/"))
+ continue;
+
+ face_file = g_file_get_child (dir, g_file_info_get_name (info));
+ g_list_store_append (faces, face_file);
+ added_faces = TRUE;
+ }
+
+ g_file_enumerator_close (enumerator, NULL, NULL);
+
+ if (added_faces && !add_all)
+ break;
+ }
+ return added_faces;
+}
+
+static void
+setup_photo_popup (UmPhotoDialog *um)
+{
+ g_auto(GStrv) facesdirs;
+ gboolean added_faces = FALSE;
+
+ um->faces = g_list_store_new (G_TYPE_FILE);
+ gtk_flow_box_bind_model (GTK_FLOW_BOX (um->flowbox),
+ G_LIST_MODEL (um->faces),
+ create_face_widget,
+ um,
+ NULL);
+
+ g_signal_connect (um->flowbox, "child-activated",
+ G_CALLBACK (face_widget_activated), um);
+
+ um->recent_faces = g_list_store_new (G_TYPE_FILE);
+ gtk_flow_box_bind_model (GTK_FLOW_BOX (um->recent_pictures),
+ G_LIST_MODEL (um->recent_faces),
+ create_face_widget,
+ um,
+ NULL);
+ g_signal_connect (um->recent_pictures, "child-activated",
+ G_CALLBACK (generated_avatar_activated), um);
+ um->custom_avatar_was_chosen = FALSE;
+
+ facesdirs = get_settings_facesdirs ();
+ added_faces = add_faces_from_dirs (um->faces, facesdirs, TRUE);
+
+ if (!added_faces) {
+ facesdirs = get_system_facesdirs ();
+ add_faces_from_dirs (um->faces, facesdirs, FALSE);
+ }
+}
+
+void
+um_photo_dialog_generate_avatar (UmPhotoDialog *um,
+ const gchar *name)
+{
+ cairo_surface_t *surface;
+ gchar *filename;
+
+ surface = generate_user_picture (name);
+
+ /* Save into a tmp file that later gets copied by AccountsService */
+ filename = g_build_filename (g_get_user_runtime_dir (), "avatar.png", NULL);
+ um->generated_avatar = g_file_new_for_path (filename);
+ cairo_surface_write_to_png (surface, g_file_get_path (um->generated_avatar));
+ g_free (filename);
+
+ /* Overwrite the first item */
+ if (g_list_model_get_item (G_LIST_MODEL (um->recent_faces), 0) != NULL)
+ g_list_store_remove (um->recent_faces, 0);
+
+ g_list_store_insert (um->recent_faces, 0,
+ um->generated_avatar);
+
+ if (!um->custom_avatar_was_chosen) {
+ um->callback (NULL, g_file_get_path (um->generated_avatar), um->data);
+ }
+}
+
+UmPhotoDialog *
+um_photo_dialog_new (SelectAvatarCallback callback,
+ gpointer data)
+{
+ UmPhotoDialog *um;
+
+ um = g_object_new (UM_TYPE_PHOTO_DIALOG, NULL);
+
+ setup_photo_popup (um);
+
+ um->callback = callback;
+ um->data = data;
+
+ return um;
+}
+
+void
+um_photo_dialog_dispose (GObject *object)
+{
+ G_OBJECT_CLASS (um_photo_dialog_parent_class)->dispose (object);
+}
+
+static void
+um_photo_dialog_init (UmPhotoDialog *um)
+{
+ gtk_widget_init_template (GTK_WIDGET (um));
+}
+
+static void
+um_photo_dialog_class_init (UmPhotoDialogClass *klass)
+{
+ GtkWidgetClass *wclass = GTK_WIDGET_CLASS (klass);
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+
+ gtk_widget_class_set_template_from_resource (wclass, "/org/gnome/initial-setup/gis-account-avatar-chooser.ui");
+
+ gtk_widget_class_bind_template_child (wclass, UmPhotoDialog, flowbox);
+ gtk_widget_class_bind_template_child (wclass, UmPhotoDialog, recent_pictures);
+ gtk_widget_class_bind_template_child (wclass, UmPhotoDialog, take_picture_button);
+ gtk_widget_class_bind_template_callback (wclass, webcam_icon_selected);
+
+ oclass->dispose = um_photo_dialog_dispose;
+}
diff --git a/gnome-initial-setup/pages/account/um-photo-dialog.h b/gnome-initial-setup/pages/account/um-photo-dialog.h
new file mode 100644
index 0000000..75061ff
--- /dev/null
+++ b/gnome-initial-setup/pages/account/um-photo-dialog.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2009-2010 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA
+ *
+ * Written by: Matthias Clasen <mclasen@redhat.com>
+ */
+
+#ifndef __UM_PHOTO_DIALOG_H__
+#define __UM_PHOTO_DIALOG_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define UM_TYPE_PHOTO_DIALOG (um_photo_dialog_get_type())
+
+G_DECLARE_FINAL_TYPE (UmPhotoDialog, um_photo_dialog, UM, PHOTO_DIALOG, GtkPopover)
+
+typedef struct _UmPhotoDialog UmPhotoDialog;
+typedef void (SelectAvatarCallback) (GdkPixbuf *pixbuf,
+ const gchar *filename,
+ gpointer data);
+
+UmPhotoDialog *um_photo_dialog_new (SelectAvatarCallback callback,
+ gpointer data);
+void um_photo_dialog_free (UmPhotoDialog *dialog);
+
+void um_photo_dialog_generate_avatar (UmPhotoDialog *dialog,
+ const gchar *name);
+
+G_END_DECLS
+
+#endif
diff --git a/gnome-initial-setup/pages/account/um-realm-manager.c b/gnome-initial-setup/pages/account/um-realm-manager.c
new file mode 100644
index 0000000..bc4fd33
--- /dev/null
+++ b/gnome-initial-setup/pages/account/um-realm-manager.c
@@ -0,0 +1,878 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2009-2012 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 2 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/>.
+ *
+ * Written by: Matthias Clasen <mclasen@redhat.com>
+ * Stef Walter <stefw@gnome.org>
+ */
+
+#include "config.h"
+
+#include "um-realm-manager.h"
+
+#include <krb5/krb5.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <fcntl.h>
+
+
+struct _UmRealmManager {
+ UmRealmObjectManagerClient parent;
+ UmRealmProvider *provider;
+};
+
+typedef struct {
+ UmRealmProviderProxyClass parent_class;
+} UmRealmManagerClass;
+
+enum {
+ REALM_ADDED,
+ NUM_SIGNALS,
+};
+
+static gint signals[NUM_SIGNALS] = { 0, };
+
+G_DEFINE_TYPE (UmRealmManager, um_realm_manager, UM_REALM_TYPE_OBJECT_MANAGER_CLIENT);
+
+GQuark
+um_realm_error_get_quark (void)
+{
+ static GQuark quark = 0;
+ if (quark == 0)
+ quark = g_quark_from_static_string ("um-realm-error");
+ return quark;
+}
+
+static gboolean
+is_realm_with_kerberos_and_membership (gpointer object)
+{
+ GDBusInterface *interface;
+
+ if (!G_IS_DBUS_OBJECT (object))
+ return FALSE;
+
+ interface = g_dbus_object_get_interface (object, "org.freedesktop.realmd.Kerberos");
+ if (interface == NULL)
+ return FALSE;
+ g_object_unref (interface);
+
+ interface = g_dbus_object_get_interface (object, "org.freedesktop.realmd.KerberosMembership");
+ if (interface == NULL)
+ return FALSE;
+ g_object_unref (interface);
+
+ return TRUE;
+}
+
+static void
+on_interface_added (GDBusObjectManager *manager,
+ GDBusObject *object,
+ GDBusInterface *interface)
+{
+ g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (interface), G_MAXINT);
+}
+
+static void
+on_object_added (GDBusObjectManager *manager,
+ GDBusObject *object,
+ gpointer user_data)
+{
+ GList *interfaces, *l;
+
+ interfaces = g_dbus_object_get_interfaces (object);
+ for (l = interfaces; l != NULL; l = g_list_next (l))
+ on_interface_added (manager, object, l->data);
+ g_list_free_full (interfaces, g_object_unref);
+
+ if (is_realm_with_kerberos_and_membership (object)) {
+ g_debug ("Saw realm: %s", g_dbus_object_get_object_path (object));
+ g_signal_emit (user_data, signals[REALM_ADDED], 0, object);
+ }
+}
+
+static void
+um_realm_manager_init (UmRealmManager *self)
+{
+ g_signal_connect (self, "object-added", G_CALLBACK (on_object_added), self);
+ g_signal_connect (self, "interface-added", G_CALLBACK (on_interface_added), self);
+}
+
+static void
+um_realm_manager_dispose (GObject *obj)
+{
+ UmRealmManager *self = UM_REALM_MANAGER (obj);
+
+ g_clear_object (&self->provider);
+
+ G_OBJECT_CLASS (um_realm_manager_parent_class)->dispose (obj);
+}
+
+static void
+um_realm_manager_class_init (UmRealmManagerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = um_realm_manager_dispose;
+
+ signals[REALM_ADDED] = g_signal_new ("realm-added", UM_TYPE_REALM_MANAGER,
+ G_SIGNAL_RUN_FIRST, 0, NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 1, UM_REALM_TYPE_OBJECT);
+}
+
+typedef struct {
+ GCancellable *cancellable;
+ UmRealmManager *manager;
+} NewClosure;
+
+static void
+new_closure_free (gpointer data)
+{
+ NewClosure *closure = data;
+ g_clear_object (&closure->cancellable);
+ g_clear_object (&closure->manager);
+ g_slice_free (NewClosure, closure);
+}
+
+static void
+on_provider_new (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *async = G_SIMPLE_ASYNC_RESULT (user_data);
+ NewClosure *closure = g_simple_async_result_get_op_res_gpointer (async);
+ GError *error = NULL;
+ UmRealmProvider *provider;
+
+ provider = um_realm_provider_proxy_new_finish (result, &error);
+ closure->manager->provider = provider;
+
+ if (error == NULL) {
+ g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (closure->manager->provider), -1);
+ g_debug ("Created realm manager");
+ } else {
+ g_simple_async_result_take_error (async, error);
+ }
+ g_simple_async_result_complete (async);
+
+ g_object_unref (async);
+}
+
+static void
+on_manager_new (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *async = G_SIMPLE_ASYNC_RESULT (user_data);
+ NewClosure *closure = g_simple_async_result_get_op_res_gpointer (async);
+ GDBusConnection *connection;
+ GError *error = NULL;
+ GObject *object;
+
+ object = g_async_initable_new_finish (G_ASYNC_INITABLE (source), result, &error);
+ if (error == NULL) {
+ closure->manager = UM_REALM_MANAGER (object);
+ connection = g_dbus_object_manager_client_get_connection (G_DBUS_OBJECT_MANAGER_CLIENT (object));
+
+ g_debug ("Connected to realmd");
+
+ um_realm_provider_proxy_new (connection,
+ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
+ "org.freedesktop.realmd",
+ "/org/freedesktop/realmd",
+ closure->cancellable,
+ on_provider_new, g_object_ref (async));
+ } else {
+ g_simple_async_result_take_error (async, error);
+ g_simple_async_result_complete (async);
+ }
+
+ g_object_unref (async);
+}
+
+void
+um_realm_manager_new (GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *async;
+ NewClosure *closure;
+
+ g_debug ("Connecting to realmd...");
+
+ async = g_simple_async_result_new (NULL, callback, user_data,
+ um_realm_manager_new);
+ closure = g_slice_new (NewClosure);
+ closure->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
+ g_simple_async_result_set_op_res_gpointer (async, closure, new_closure_free);
+
+ g_async_initable_new_async (UM_TYPE_REALM_MANAGER, G_PRIORITY_DEFAULT,
+ cancellable, on_manager_new, g_object_ref (async),
+ "flags", G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
+ "name", "org.freedesktop.realmd",
+ "bus-type", G_BUS_TYPE_SYSTEM,
+ "object-path", "/org/freedesktop/realmd",
+ "get-proxy-type-func", um_realm_object_manager_client_get_proxy_type,
+ NULL);
+
+ g_object_unref (async);
+}
+
+UmRealmManager *
+um_realm_manager_new_finish (GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *async;
+ NewClosure *closure;
+
+ g_return_val_if_fail (g_simple_async_result_is_valid (result, NULL,
+ um_realm_manager_new), NULL);
+
+ async = G_SIMPLE_ASYNC_RESULT (result);
+ if (g_simple_async_result_propagate_error (async, error))
+ return NULL;
+
+ closure = g_simple_async_result_get_op_res_gpointer (async);
+ return g_object_ref (closure->manager);
+}
+
+typedef struct {
+ UmRealmManager *manager;
+ GCancellable *cancellable;
+ GList *realms;
+} DiscoverClosure;
+
+static void
+discover_closure_free (gpointer data)
+{
+ DiscoverClosure *discover = data;
+ g_object_unref (discover->manager);
+ g_clear_object (&discover->cancellable);
+ g_list_free_full (discover->realms, g_object_unref);
+ g_slice_free (DiscoverClosure, discover);
+}
+
+static void
+on_provider_discover (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *async = G_SIMPLE_ASYNC_RESULT (user_data);
+ DiscoverClosure *discover = g_simple_async_result_get_op_res_gpointer (async);
+ GDBusObject *object;
+ GError *error = NULL;
+ gboolean no_membership = FALSE;
+ gchar **realms;
+ gint relevance;
+ gint i;
+
+ um_realm_provider_call_discover_finish (UM_REALM_PROVIDER (source), &relevance,
+ &realms, result, &error);
+ if (error == NULL) {
+ for (i = 0; realms[i]; i++) {
+ object = g_dbus_object_manager_get_object (G_DBUS_OBJECT_MANAGER (discover->manager), realms[i]);
+ if (object == NULL) {
+ g_warning ("Realm is not in object manager: %s", realms[i]);
+ } else {
+ if (is_realm_with_kerberos_and_membership (object)) {
+ g_debug ("Discovered realm: %s", realms[i]);
+ discover->realms = g_list_prepend (discover->realms, object);
+ } else {
+ g_debug ("Realm does not support kerberos membership: %s", realms[i]);
+ no_membership = TRUE;
+ g_object_unref (object);
+ }
+ }
+ }
+ g_strfreev (realms);
+
+ if (!discover->realms && no_membership) {
+ g_simple_async_result_set_error (async, UM_REALM_ERROR, UM_REALM_ERROR_GENERIC,
+ _("Cannot automatically join this type of domain"));
+ }
+ } else {
+ g_simple_async_result_take_error (async, error);
+ }
+
+ g_simple_async_result_complete (async);
+ g_object_unref (async);
+}
+
+void
+um_realm_manager_discover (UmRealmManager *self,
+ const gchar *input,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *res;
+ DiscoverClosure *discover;
+ GVariant *options;
+
+ g_return_if_fail (UM_IS_REALM_MANAGER (self));
+ g_return_if_fail (input != NULL);
+ g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+ g_debug ("Discovering realms for: %s", input);
+
+ res = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
+ um_realm_manager_discover);
+ discover = g_slice_new0 (DiscoverClosure);
+ discover->manager = g_object_ref (self);
+ discover->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
+ g_simple_async_result_set_op_res_gpointer (res, discover, discover_closure_free);
+
+ options = g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0);
+
+ um_realm_provider_call_discover (self->provider, input, options, cancellable,
+ on_provider_discover, g_object_ref (res));
+
+ g_object_unref (res);
+}
+
+GList *
+um_realm_manager_discover_finish (UmRealmManager *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *async;
+ DiscoverClosure *discover;
+ GList *realms;
+
+ g_return_val_if_fail (UM_IS_REALM_MANAGER (self), NULL);
+ g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (self),
+ um_realm_manager_discover), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ async = G_SIMPLE_ASYNC_RESULT (result);
+ if (g_simple_async_result_propagate_error (async, error))
+ return NULL;
+
+ discover = g_simple_async_result_get_op_res_gpointer (async);
+ if (!discover->realms) {
+ g_set_error (error, UM_REALM_ERROR, UM_REALM_ERROR_GENERIC,
+ _("No such domain or realm found"));
+ return NULL;
+ }
+
+ realms = g_list_reverse (discover->realms);
+ discover->realms = NULL;
+ return realms;
+}
+
+GList *
+um_realm_manager_get_realms (UmRealmManager *self)
+{
+ GList *objects;
+ GList *realms = NULL;
+ GList *l;
+
+ g_return_val_if_fail (UM_IS_REALM_MANAGER (self), NULL);
+
+ objects = g_dbus_object_manager_get_objects (G_DBUS_OBJECT_MANAGER (self));
+ for (l = objects; l != NULL; l = g_list_next (l)) {
+ if (is_realm_with_kerberos_and_membership (l->data))
+ realms = g_list_prepend (realms, g_object_ref (l->data));
+ }
+
+ g_list_free_full (objects, g_object_unref);
+ return realms;
+}
+
+static void
+string_replace (GString *string,
+ const gchar *find,
+ const gchar *replace)
+{
+ const gchar *at;
+ gssize pos;
+
+ at = strstr (string->str, find);
+ if (at != NULL) {
+ pos = at - string->str;
+ g_string_erase (string, pos, strlen (find));
+ g_string_insert (string, pos, replace);
+ }
+}
+
+gchar *
+um_realm_calculate_login (UmRealmCommon *realm,
+ const gchar *username)
+{
+ GString *string;
+ const gchar *const *formats;
+ gchar *login = NULL;
+
+ formats = um_realm_common_get_login_formats (realm);
+ if (formats[0] != NULL) {
+ string = g_string_new (formats[0]);
+ string_replace (string, "%U", username);
+ string_replace (string, "%D", um_realm_common_get_name (realm));
+ login = g_string_free (string, FALSE);
+ }
+
+ return login;
+
+}
+
+gboolean
+um_realm_is_configured (UmRealmObject *realm)
+{
+ UmRealmCommon *common;
+ const gchar *configured;
+ gboolean is = FALSE;
+
+ common = um_realm_object_get_common (realm);
+ if (common) {
+ configured = um_realm_common_get_configured (common);
+ is = configured != NULL && !g_str_equal (configured, "");
+ g_object_unref (common);
+ }
+
+ return is;
+}
+
+static const gchar *
+find_supported_credentials (UmRealmKerberosMembership *membership,
+ const gchar *owner)
+{
+ const gchar *cred_owner;
+ const gchar *cred_type;
+ GVariant *supported;
+ GVariantIter iter;
+
+ supported = um_realm_kerberos_membership_get_supported_join_credentials (membership);
+ g_return_val_if_fail (supported != NULL, NULL);
+
+ g_variant_iter_init (&iter, supported);
+ while (g_variant_iter_loop (&iter, "(&s&s)", &cred_type, &cred_owner)) {
+ if (g_str_equal (owner, cred_owner)) {
+ if (g_str_equal (cred_type, "ccache") ||
+ g_str_equal (cred_type, "password")) {
+ return g_intern_string (cred_type);
+ }
+ }
+ }
+
+ return NULL;
+}
+
+static void
+on_realm_join_complete (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *async = G_SIMPLE_ASYNC_RESULT (user_data);
+
+ g_debug ("Completed Join() method call");
+
+ g_simple_async_result_set_op_res_gpointer (async, g_object_ref (result), g_object_unref);
+ g_simple_async_result_complete_in_idle (async);
+ g_object_unref (async);
+}
+
+static gboolean
+realm_join_as_owner (UmRealmObject *realm,
+ const gchar *owner,
+ const gchar *login,
+ const gchar *password,
+ GBytes *credentials,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ UmRealmKerberosMembership *membership;
+ GSimpleAsyncResult *async;
+ GVariant *contents;
+ GVariant *options;
+ GVariant *option;
+ GVariant *creds;
+ const gchar *type;
+
+ membership = um_realm_object_get_kerberos_membership (realm);
+ g_return_val_if_fail (membership != NULL, FALSE);
+
+ type = find_supported_credentials (membership, owner);
+ if (type == NULL) {
+ g_debug ("Couldn't find supported credential type for owner: %s", owner);
+ g_object_unref (membership);
+ return FALSE;
+ }
+
+ async = g_simple_async_result_new (G_OBJECT (realm), callback, user_data,
+ realm_join_as_owner);
+
+ if (g_str_equal (type, "ccache")) {
+ g_debug ("Using a kerberos credential cache to join the realm");
+ contents = g_variant_new_from_data (G_VARIANT_TYPE ("ay"),
+ g_bytes_get_data (credentials, NULL),
+ g_bytes_get_size (credentials),
+ TRUE, (GDestroyNotify)g_bytes_unref, credentials);
+
+ } else if (g_str_equal (type, "password")) {
+ g_debug ("Using a user/password to join the realm");
+ contents = g_variant_new ("(ss)", login, password);
+
+ } else {
+ g_assert_not_reached ();
+ }
+
+ creds = g_variant_new ("(ssv)", type, owner, contents);
+ option = g_variant_new ("{sv}", "manage-system", g_variant_new_boolean (FALSE));
+ options = g_variant_new_array (G_VARIANT_TYPE ("{sv}"), &option, 1);
+
+ g_debug ("Calling the Join() method with %s credentials", owner);
+
+ um_realm_kerberos_membership_call_join (membership, creds, options,
+ cancellable, on_realm_join_complete,
+ g_object_ref (async));
+
+ g_object_unref (async);
+ g_object_unref (membership);
+
+ return TRUE;
+}
+
+gboolean
+um_realm_join_as_user (UmRealmObject *realm,
+ const gchar *login,
+ const gchar *password,
+ GBytes *credentials,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_return_val_if_fail (UM_REALM_IS_OBJECT (realm), FALSE);
+ g_return_val_if_fail (credentials != NULL, FALSE);
+ g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
+ g_return_val_if_fail (login != NULL, FALSE);
+ g_return_val_if_fail (password != NULL, FALSE);
+ g_return_val_if_fail (credentials != NULL, FALSE);
+
+ return realm_join_as_owner (realm, "user", login, password,
+ credentials, cancellable, callback, user_data);
+}
+
+gboolean
+um_realm_join_as_admin (UmRealmObject *realm,
+ const gchar *login,
+ const gchar *password,
+ GBytes *credentials,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_return_val_if_fail (UM_REALM_IS_OBJECT (realm), FALSE);
+ g_return_val_if_fail (credentials != NULL, FALSE);
+ g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
+ g_return_val_if_fail (login != NULL, FALSE);
+ g_return_val_if_fail (password != NULL, FALSE);
+ g_return_val_if_fail (credentials != NULL, FALSE);
+
+ return realm_join_as_owner (realm, "administrator", login, password, credentials,
+ cancellable, callback, user_data);
+}
+
+gboolean
+um_realm_join_finish (UmRealmObject *realm,
+ GAsyncResult *result,
+ GError **error)
+{
+ UmRealmKerberosMembership *membership;
+ GError *call_error = NULL;
+ gchar *dbus_error;
+ GAsyncResult *async;
+
+ g_return_val_if_fail (UM_REALM_IS_OBJECT (realm), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ membership = um_realm_object_get_kerberos_membership (realm);
+ g_return_val_if_fail (membership != NULL, FALSE);
+
+ async = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result));
+ um_realm_kerberos_membership_call_join_finish (membership, async, &call_error);
+ g_object_unref (membership);
+
+ if (call_error == NULL)
+ return TRUE;
+
+ dbus_error = g_dbus_error_get_remote_error (call_error);
+ if (dbus_error == NULL) {
+ g_debug ("Join() failed because of %s", call_error->message);
+ g_propagate_error (error, call_error);
+ return FALSE;
+ }
+
+ g_dbus_error_strip_remote_error (call_error);
+
+ if (g_str_equal (dbus_error, "org.freedesktop.realmd.Error.AuthenticationFailed")) {
+ g_debug ("Join() failed because of invalid/insufficient credentials");
+ g_set_error (error, UM_REALM_ERROR, UM_REALM_ERROR_BAD_LOGIN,
+ "%s", call_error->message);
+ g_error_free (call_error);
+ } else if (g_str_equal (dbus_error, "org.freedesktop.realmd.Error.BadHostname")) {
+ g_debug ("Join() failed because of invalid/conflicting host name");
+ g_set_error (error, UM_REALM_ERROR, UM_REALM_ERROR_BAD_HOSTNAME,
+ "%s", call_error->message);
+ g_error_free (call_error);
+ } else {
+ g_debug ("Join() failed because of %s", call_error->message);
+ g_propagate_error (error, call_error);
+ }
+
+ g_free (dbus_error);
+ return FALSE;
+}
+
+typedef struct {
+ gchar *domain;
+ gchar *realm;
+ gchar *user;
+ gchar *password;
+ GBytes *credentials;
+} LoginClosure;
+
+static void
+login_closure_free (gpointer data)
+{
+ LoginClosure *login = data;
+ g_free (login->domain);
+ g_free (login->realm);
+ g_free (login->user);
+ g_free (login->password);
+ g_bytes_unref (login->credentials);
+ g_slice_free (LoginClosure, login);
+}
+
+static krb5_error_code
+login_perform_kinit (krb5_context k5,
+ const gchar *realm,
+ const gchar *login,
+ const gchar *password,
+ const gchar *filename)
+{
+ krb5_get_init_creds_opt *opts;
+ krb5_error_code code;
+ krb5_principal principal;
+ krb5_ccache ccache;
+ krb5_creds creds;
+ gchar *name;
+
+ name = g_strdup_printf ("%s@%s", login, realm);
+ code = krb5_parse_name (k5, name, &principal);
+
+ if (code != 0) {
+ g_debug ("Couldn't parse principal name: %s: %s",
+ name, krb5_get_error_message (k5, code));
+ g_free (name);
+ return code;
+ }
+
+ g_debug ("Using principal name to kinit: %s", name);
+ g_free (name);
+
+ if (filename == NULL)
+ code = krb5_cc_default (k5, &ccache);
+ else
+ code = krb5_cc_resolve (k5, filename, &ccache);
+
+ if (code != 0) {
+ krb5_free_principal (k5, principal);
+ g_debug ("Couldn't open credential cache: %s: %s",
+ filename ? filename : "<default>",
+ krb5_get_error_message (k5, code));
+ return code;
+ }
+
+ code = krb5_get_init_creds_opt_alloc (k5, &opts);
+ g_return_val_if_fail (code == 0, code);
+
+ code = krb5_get_init_creds_opt_set_out_ccache (k5, opts, ccache);
+ g_return_val_if_fail (code == 0, code);
+
+ code = krb5_get_init_creds_password (k5, &creds, principal,
+ (char *)password,
+ NULL, 0, 0, NULL, opts);
+
+ krb5_get_init_creds_opt_free (k5, opts);
+ krb5_cc_close (k5, ccache);
+ krb5_free_principal (k5, principal);
+
+ if (code == 0) {
+ g_debug ("kinit succeeded");
+ krb5_free_cred_contents (k5, &creds);
+ } else {
+ g_debug ("kinit failed: %s", krb5_get_error_message (k5, code));
+ }
+
+ return code;
+}
+
+static void
+kinit_thread_func (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ LoginClosure *login = task_data;
+ krb5_context k5 = NULL;
+ krb5_error_code code;
+ GError *error = NULL;
+ gchar *filename = NULL;
+ gchar *contents;
+ gsize length;
+ gint temp_fd;
+
+ filename = g_build_filename (g_get_user_runtime_dir (),
+ "um-krb5-creds.XXXXXX", NULL);
+ temp_fd = g_mkstemp_full (filename, O_RDWR, S_IRUSR | S_IWUSR);
+ if (temp_fd == -1) {
+ g_warning ("Couldn't create credential cache file: %s: %s",
+ filename, g_strerror (errno));
+ g_free (filename);
+ filename = NULL;
+ } else {
+ close (temp_fd);
+ }
+
+ code = krb5_init_context (&k5);
+ if (code == 0) {
+ code = login_perform_kinit (k5, login->realm, login->user,
+ login->password, filename);
+ }
+
+ switch (code) {
+ case 0:
+ if (filename != NULL) {
+ g_file_get_contents (filename, &contents, &length, &error);
+ if (error == NULL) {
+ login->credentials = g_bytes_new_take (contents, length);
+ g_debug ("Read in credential cache: %s", filename);
+ } else {
+ g_warning ("Couldn't read credential cache: %s: %s",
+ filename, error->message);
+ g_error_free (error);
+ }
+ }
+ g_task_return_boolean (task, TRUE);
+ break;
+
+ case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN:
+ case KRB5KDC_ERR_POLICY:
+ g_task_return_new_error (task, UM_REALM_ERROR, UM_REALM_ERROR_BAD_LOGIN,
+ _("Cannot log in as %s at the %s domain"),
+ login->user, login->domain);
+ break;
+ case KRB5KDC_ERR_PREAUTH_FAILED:
+ case KRB5KRB_AP_ERR_BAD_INTEGRITY:
+ g_task_return_new_error (task, UM_REALM_ERROR, UM_REALM_ERROR_BAD_PASSWORD,
+ _("Invalid password, please try again"));
+ break;
+ case KRB5_PREAUTH_FAILED:
+ case KRB5KDC_ERR_KEY_EXP:
+ case KRB5KDC_ERR_CLIENT_REVOKED:
+ case KRB5KDC_ERR_ETYPE_NOSUPP:
+ case KRB5_PROG_ETYPE_NOSUPP:
+ g_task_return_new_error (task, UM_REALM_ERROR, UM_REALM_ERROR_CANNOT_AUTH,
+ _("Cannot log in as %s at the %s domain"),
+ login->user, login->domain);
+ break;
+ default:
+ g_task_return_new_error (task, UM_REALM_ERROR, UM_REALM_ERROR_GENERIC,
+ _("Couldn’t connect to the %s domain: %s"),
+ login->domain, krb5_get_error_message (k5, code));
+ break;
+ }
+
+ if (filename) {
+ g_unlink (filename);
+ g_debug ("Deleted credential cache: %s", filename);
+ g_free (filename);
+ }
+
+ if (k5)
+ krb5_free_context (k5);
+}
+
+void
+um_realm_login (UmRealmObject *realm,
+ const gchar *user,
+ const gchar *password,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ LoginClosure *login;
+ UmRealmKerberos *kerberos;
+
+ g_return_if_fail (UM_REALM_IS_OBJECT (realm));
+ g_return_if_fail (user != NULL);
+ g_return_if_fail (password != NULL);
+ g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+ kerberos = um_realm_object_get_kerberos (realm);
+ g_return_if_fail (kerberos != NULL);
+
+ task = g_task_new (realm, cancellable, callback, user_data);
+ login = g_slice_new0 (LoginClosure);
+ login->domain = g_strdup (um_realm_kerberos_get_domain_name (kerberos));
+ login->realm = g_strdup (um_realm_kerberos_get_realm_name (kerberos));
+ login->user = g_strdup (user);
+ login->password = g_strdup (password);
+ g_task_set_task_data (task, login, login_closure_free);
+
+ g_task_set_check_cancellable (task, TRUE);
+ g_task_set_return_on_cancel (task, TRUE);
+
+ g_task_run_in_thread (task, kinit_thread_func);
+
+ g_object_unref (task);
+ g_object_unref (kerberos);
+}
+
+gboolean
+um_realm_login_finish (UmRealmObject *realm,
+ GAsyncResult *result,
+ GBytes **credentials,
+ GError **error)
+{
+ GTask *task;
+ LoginClosure *login;
+
+ g_return_val_if_fail (g_task_is_valid (result, realm), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ task = G_TASK (result);
+ if (!g_task_propagate_boolean (task, error))
+ return FALSE;
+
+ login = g_task_get_task_data (task);
+ if (credentials) {
+ if (login->credentials)
+ *credentials = g_bytes_ref (login->credentials);
+ else
+ *credentials = NULL;
+ }
+
+ return TRUE;
+}
diff --git a/gnome-initial-setup/pages/account/um-realm-manager.h b/gnome-initial-setup/pages/account/um-realm-manager.h
new file mode 100644
index 0000000..952bd2f
--- /dev/null
+++ b/gnome-initial-setup/pages/account/um-realm-manager.h
@@ -0,0 +1,108 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2012 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 2 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/>.
+ *
+ * Written by: Stef Walter <stefw@gnome.org>
+ */
+
+#ifndef __UM_REALM_MANAGER_H__
+#define __UM_REALM_MANAGER_H__
+
+#include "um-realm-generated.h"
+
+G_BEGIN_DECLS
+
+typedef enum {
+ UM_REALM_ERROR_BAD_LOGIN,
+ UM_REALM_ERROR_BAD_PASSWORD,
+ UM_REALM_ERROR_CANNOT_AUTH,
+ UM_REALM_ERROR_BAD_HOSTNAME,
+ UM_REALM_ERROR_GENERIC,
+} UmRealmErrors;
+
+#define UM_REALM_ERROR (um_realm_error_get_quark ())
+
+GQuark um_realm_error_get_quark (void) G_GNUC_CONST;
+
+#define UM_TYPE_REALM_MANAGER (um_realm_manager_get_type ())
+#define UM_REALM_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), UM_TYPE_REALM_MANAGER, UmRealmManager))
+#define UM_IS_REALM_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), UM_TYPE_REALM_MANAGER))
+
+typedef struct _UmRealmManager UmRealmManager;
+
+GType um_realm_manager_get_type (void) G_GNUC_CONST;
+
+void um_realm_manager_new (GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+UmRealmManager * um_realm_manager_new_finish (GAsyncResult *result,
+ GError **error);
+
+void um_realm_manager_discover (UmRealmManager *self,
+ const gchar *input,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+GList * um_realm_manager_discover_finish (UmRealmManager *self,
+ GAsyncResult *result,
+ GError **error);
+
+GList * um_realm_manager_get_realms (UmRealmManager *self);
+
+void um_realm_login (UmRealmObject *realm,
+ const gchar *login,
+ const gchar *password,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean um_realm_login_finish (UmRealmObject *realm,
+ GAsyncResult *result,
+ GBytes **credentials,
+ GError **error);
+
+gboolean um_realm_join_as_user (UmRealmObject *realm,
+ const gchar *login,
+ const gchar *password,
+ GBytes *credentials,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+ G_GNUC_WARN_UNUSED_RESULT;
+
+gboolean um_realm_join_as_admin (UmRealmObject *realm,
+ const gchar *login,
+ const gchar *password,
+ GBytes *credentials,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+ G_GNUC_WARN_UNUSED_RESULT;
+
+gboolean um_realm_join_finish (UmRealmObject *realm,
+ GAsyncResult *result,
+ GError **error);
+
+gboolean um_realm_is_configured (UmRealmObject *realm);
+
+gchar * um_realm_calculate_login (UmRealmCommon *realm,
+ const gchar *username);
+
+G_END_DECLS
+
+#endif /* __UM_REALM_H__ */
diff --git a/gnome-initial-setup/pages/account/um-utils.c b/gnome-initial-setup/pages/account/um-utils.c
new file mode 100644
index 0000000..2877d94
--- /dev/null
+++ b/gnome-initial-setup/pages/account/um-utils.c
@@ -0,0 +1,568 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2009-2010 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA
+ *
+ * Written by: Matthias Clasen <mclasen@redhat.com>
+ */
+
+#include "config.h"
+
+#include <math.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <utmp.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include "um-utils.h"
+
+void
+set_entry_validation_checkmark (GtkEntry *entry)
+{
+ gtk_entry_set_icon_from_icon_name (entry,
+ GTK_ENTRY_ICON_SECONDARY,
+ "object-select-symbolic");
+}
+
+void
+set_entry_validation_error (GtkEntry *entry,
+ const gchar *text)
+{
+ gtk_entry_set_icon_from_icon_name (entry,
+ GTK_ENTRY_ICON_SECONDARY,
+ "dialog-warning-symbolic");
+ gtk_entry_set_icon_tooltip_text (entry,
+ GTK_ENTRY_ICON_SECONDARY,
+ text);
+}
+
+void
+clear_entry_validation_error (GtkEntry *entry)
+{
+ gtk_entry_set_icon_from_paintable (entry,
+ GTK_ENTRY_ICON_SECONDARY,
+ NULL);
+}
+
+#define MAXNAMELEN (UT_NAMESIZE - 1)
+
+static gboolean
+is_username_used (const gchar *username)
+{
+ struct passwd *pwent;
+
+ if (username == NULL || username[0] == '\0') {
+ return FALSE;
+ }
+
+ pwent = getpwnam (username);
+
+ return pwent != NULL;
+}
+
+gboolean
+is_valid_name (const gchar *name)
+{
+ gboolean is_empty = TRUE;
+ const gchar *c;
+
+ /* Valid names must contain:
+ * 1) at least one character.
+ * 2) at least one non-"space" character.
+ */
+ for (c = name; *c; c++) {
+ gunichar unichar;
+
+ unichar = g_utf8_get_char_validated (c, -1);
+
+ /* Partial UTF-8 sequence or end of string */
+ if (unichar == (gunichar) -1 || unichar == (gunichar) -2)
+ break;
+
+ /* Check for non-space character */
+ if (!g_unichar_isspace (unichar)) {
+ is_empty = FALSE;
+ break;
+ }
+ }
+
+ return !is_empty;
+}
+
+gboolean
+is_valid_username (const gchar *username, gboolean parental_controls_enabled, gchar **tip)
+{
+ gboolean empty;
+ gboolean in_use;
+ gboolean too_long;
+ gboolean valid;
+ gboolean parental_controls_conflict;
+ const gchar *c;
+
+ if (username == NULL || username[0] == '\0') {
+ empty = TRUE;
+ in_use = FALSE;
+ too_long = FALSE;
+ } else {
+ empty = FALSE;
+ in_use = is_username_used (username);
+ too_long = strlen (username) > MAXNAMELEN;
+ }
+ valid = TRUE;
+
+ if (!in_use && !empty && !too_long) {
+ /* First char must be a lower case letter, and it must only be
+ * composed of lower case letters, digits, '-', and '_'.
+ */
+ for (c = username; *c; c++) {
+ if (c == username) {
+ if (! (*c >= 'a' && *c <= 'z'))
+ valid = FALSE;
+ } else {
+ if (! ((*c >= 'a' && *c <= 'z') ||
+ (*c >= '0' && *c <= '9') ||
+ (*c == '_') || (*c == '-')))
+ valid = FALSE;
+ }
+ }
+ }
+
+ parental_controls_conflict = (parental_controls_enabled && g_strcmp0 (username, "administrator") == 0);
+
+ valid = !empty && !in_use && !too_long && !parental_controls_conflict && valid;
+
+ if (!empty && (in_use || too_long || parental_controls_conflict || !valid)) {
+ if (in_use) {
+ *tip = g_strdup (_("Sorry, that user name isn’t available. Please try another."));
+ }
+ else if (too_long) {
+ *tip = g_strdup_printf (_("The username is too long."));
+ }
+ else if (!(username[0] >= 'a' && username[0] <= 'z')) {
+ *tip = g_strdup (_("The username must start with a lower case letter from a-z."));
+ }
+ else if (parental_controls_conflict) {
+ *tip = g_strdup (_("That username isn’t available. Please try another."));
+ }
+ else {
+ *tip = g_strdup (_("The username should only consist of lower case letters from a-z, digits, and the following characters: - _"));
+ }
+ }
+ else {
+ *tip = g_strdup (_("This will be used to name your home folder and can’t be changed."));
+ }
+
+ return valid;
+}
+
+void
+generate_username_choices (const gchar *name,
+ GtkListStore *store)
+{
+ gboolean in_use, same_as_initial;
+ char *lc_name, *ascii_name, *stripped_name;
+ char **words1;
+ char **words2 = NULL;
+ char **w1, **w2;
+ char *c;
+ char *unicode_fallback = "?";
+ GString *first_word, *last_word;
+ GString *item0, *item1, *item2, *item3, *item4;
+ int len;
+ int nwords1, nwords2, i;
+ GHashTable *items;
+ GtkTreeIter iter;
+
+ gtk_list_store_clear (store);
+
+ ascii_name = g_convert_with_fallback (name, -1, "ASCII//TRANSLIT", "UTF-8",
+ unicode_fallback, NULL, NULL, NULL);
+
+ lc_name = g_ascii_strdown (ascii_name, -1);
+
+ /* Remove all non ASCII alphanumeric chars from the name,
+ * apart from the few allowed symbols.
+ *
+ * We do remove '.', even though it is usually allowed,
+ * since it often comes in via an abbreviated middle name,
+ * and the dot looks just wrong in the proposals then.
+ */
+ stripped_name = g_strnfill (strlen (lc_name) + 1, '\0');
+ i = 0;
+ for (c = lc_name; *c; c++) {
+ if (!(g_ascii_isdigit (*c) || g_ascii_islower (*c) ||
+ *c == ' ' || *c == '-' || *c == '_' ||
+ /* used to track invalid words, removed below */
+ *c == '?') )
+ continue;
+
+ stripped_name[i] = *c;
+ i++;
+ }
+
+ if (strlen (stripped_name) == 0) {
+ g_free (ascii_name);
+ g_free (lc_name);
+ g_free (stripped_name);
+ return;
+ }
+
+ /* we split name on spaces, and then on dashes, so that we can treat
+ * words linked with dashes the same way, i.e. both fully shown, or
+ * both abbreviated
+ */
+ words1 = g_strsplit_set (stripped_name, " ", -1);
+ len = g_strv_length (words1);
+
+ /* The default item is a concatenation of all words without ? */
+ item0 = g_string_sized_new (strlen (stripped_name));
+
+ g_free (ascii_name);
+ g_free (lc_name);
+ g_free (stripped_name);
+
+ /* Concatenate the whole first word with the first letter of each
+ * word (item1), and the last word with the first letter of each
+ * word (item2). item3 and item4 are symmetrical respectively to
+ * item1 and item2.
+ *
+ * Constant 5 is the max reasonable number of words we may get when
+ * splitting on dashes, since we can't guess it at this point,
+ * and reallocating would be too bad.
+ */
+ item1 = g_string_sized_new (strlen (words1[0]) + len - 1 + 5);
+ item3 = g_string_sized_new (strlen (words1[0]) + len - 1 + 5);
+
+ item2 = g_string_sized_new (strlen (words1[len - 1]) + len - 1 + 5);
+ item4 = g_string_sized_new (strlen (words1[len - 1]) + len - 1 + 5);
+
+ /* again, guess at the max size of names */
+ first_word = g_string_sized_new (20);
+ last_word = g_string_sized_new (20);
+
+ nwords1 = 0;
+ nwords2 = 0;
+ for (w1 = words1; *w1; w1++) {
+ if (strlen (*w1) == 0)
+ continue;
+
+ /* skip words with string '?', most likely resulting
+ * from failed transliteration to ASCII
+ */
+ if (strstr (*w1, unicode_fallback) != NULL)
+ continue;
+
+ nwords1++; /* count real words, excluding empty string */
+
+ item0 = g_string_append (item0, *w1);
+
+ words2 = g_strsplit_set (*w1, "-", -1);
+ /* reset last word if a new non-empty word has been found */
+ if (strlen (*words2) > 0)
+ last_word = g_string_set_size (last_word, 0);
+
+ for (w2 = words2; *w2; w2++) {
+ if (strlen (*w2) == 0)
+ continue;
+
+ nwords2++;
+
+ /* part of the first "toplevel" real word */
+ if (nwords1 == 1) {
+ item1 = g_string_append (item1, *w2);
+ first_word = g_string_append (first_word, *w2);
+ }
+ else {
+ item1 = g_string_append_unichar (item1,
+ g_utf8_get_char (*w2));
+ item3 = g_string_append_unichar (item3,
+ g_utf8_get_char (*w2));
+ }
+
+ /* not part of the last "toplevel" word */
+ if (w1 != words1 + len - 1) {
+ item2 = g_string_append_unichar (item2,
+ g_utf8_get_char (*w2));
+ item4 = g_string_append_unichar (item4,
+ g_utf8_get_char (*w2));
+ }
+
+ /* always save current word so that we have it if last one reveals empty */
+ last_word = g_string_append (last_word, *w2);
+ }
+
+ g_strfreev (words2);
+ }
+ item2 = g_string_append (item2, last_word->str);
+ item3 = g_string_append (item3, first_word->str);
+ item4 = g_string_prepend (item4, last_word->str);
+
+ items = g_hash_table_new (g_str_hash, g_str_equal);
+
+ in_use = is_username_used (item0->str);
+ if (!in_use && !g_ascii_isdigit (item0->str[0])) {
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter, 0, item0->str, -1);
+ g_hash_table_insert (items, item0->str, item0->str);
+ }
+
+ in_use = is_username_used (item1->str);
+ same_as_initial = (g_strcmp0 (item0->str, item1->str) == 0);
+ if (!same_as_initial && nwords2 > 0 && !in_use && !g_ascii_isdigit (item1->str[0])) {
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter, 0, item1->str, -1);
+ g_hash_table_insert (items, item1->str, item1->str);
+ }
+
+ /* if there's only one word, would be the same as item1 */
+ if (nwords2 > 1) {
+ /* add other items */
+ in_use = is_username_used (item2->str);
+ if (!in_use && !g_ascii_isdigit (item2->str[0]) &&
+ !g_hash_table_lookup (items, item2->str)) {
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter, 0, item2->str, -1);
+ g_hash_table_insert (items, item2->str, item2->str);
+ }
+
+ in_use = is_username_used (item3->str);
+ if (!in_use && !g_ascii_isdigit (item3->str[0]) &&
+ !g_hash_table_lookup (items, item3->str)) {
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter, 0, item3->str, -1);
+ g_hash_table_insert (items, item3->str, item3->str);
+ }
+
+ in_use = is_username_used (item4->str);
+ if (!in_use && !g_ascii_isdigit (item4->str[0]) &&
+ !g_hash_table_lookup (items, item4->str)) {
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter, 0, item4->str, -1);
+ g_hash_table_insert (items, item4->str, item4->str);
+ }
+
+ /* add the last word */
+ in_use = is_username_used (last_word->str);
+ if (!in_use && !g_ascii_isdigit (last_word->str[0]) &&
+ !g_hash_table_lookup (items, last_word->str)) {
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter, 0, last_word->str, -1);
+ g_hash_table_insert (items, last_word->str, last_word->str);
+ }
+
+ /* ...and the first one */
+ in_use = is_username_used (first_word->str);
+ if (!in_use && !g_ascii_isdigit (first_word->str[0]) &&
+ !g_hash_table_lookup (items, first_word->str)) {
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter, 0, first_word->str, -1);
+ g_hash_table_insert (items, first_word->str, first_word->str);
+ }
+ }
+
+ g_hash_table_destroy (items);
+ g_strfreev (words1);
+ g_string_free (first_word, TRUE);
+ g_string_free (last_word, TRUE);
+ g_string_free (item0, TRUE);
+ g_string_free (item1, TRUE);
+ g_string_free (item2, TRUE);
+ g_string_free (item3, TRUE);
+ g_string_free (item4, TRUE);
+}
+
+#define IMAGE_SIZE 512
+#define IMAGE_BORDER_WIDTH 7.2 /* At least 1px when avatar rendered at 72px */
+
+/* U+1F464 "bust in silhouette"
+ * U+FE0E Variant Selector 15 to force text style (monochrome) emoji
+ */
+#define PLACEHOLDER "\U0001F464\U0000FE0E"
+
+static gchar *
+extract_initials_from_name (const gchar *name)
+{
+ GString *initials = g_string_new ("");
+ gchar *p;
+ gchar *normalized;
+ gunichar unichar;
+
+ if (name == NULL || name[0] == '\0') {
+ g_string_free (initials, TRUE);
+ return g_strdup (PLACEHOLDER);
+ }
+
+ p = g_utf8_strup (name, -1);
+ normalized = g_utf8_normalize (g_strstrip (p), -1, G_NORMALIZE_DEFAULT_COMPOSE);
+ g_clear_pointer (&p, g_free);
+ if (normalized == NULL) {
+ g_free (normalized);
+ g_string_free (initials, TRUE);
+ return g_strdup (PLACEHOLDER);
+ }
+
+ unichar = g_utf8_get_char (normalized);
+ g_string_append_unichar (initials, unichar);
+
+ p = g_utf8_strrchr (normalized, -1, ' ');
+ if (p != NULL) {
+ p = g_utf8_next_char (p);
+
+ unichar = g_utf8_get_char (p);
+ g_string_append_unichar (initials, unichar);
+ }
+
+ g_free (normalized);
+
+ return g_string_free (initials, FALSE);
+}
+
+GdkRGBA
+get_color_for_name (const gchar *name)
+{
+ // https://gitlab.gnome.org/Community/Design/HIG-app-icons/blob/master/GNOME%20HIG.gpl
+ static gdouble gnome_color_palette[][3] = {
+ { 98, 160, 234 },
+ { 53, 132, 228 },
+ { 28, 113, 216 },
+ { 26, 95, 180 },
+ { 87, 227, 137 },
+ { 51, 209, 122 },
+ { 46, 194, 126 },
+ { 38, 162, 105 },
+ { 248, 228, 92 },
+ { 246, 211, 45 },
+ { 245, 194, 17 },
+ { 229, 165, 10 },
+ { 255, 163, 72 },
+ { 255, 120, 0 },
+ { 230, 97, 0 },
+ { 198, 70, 0 },
+ { 237, 51, 59 },
+ { 224, 27, 36 },
+ { 192, 28, 40 },
+ { 165, 29, 45 },
+ { 192, 97, 203 },
+ { 163, 71, 186 },
+ { 129, 61, 156 },
+ { 97, 53, 131 },
+ { 181, 131, 90 },
+ { 152, 106, 68 },
+ { 134, 94, 60 },
+ { 99, 69, 44 }
+ };
+
+ GdkRGBA color = { 255, 255, 255, 1.0 };
+ guint hash;
+ gint number_of_colors;
+ gint idx;
+
+ if (name == NULL || name[0] == '\0') {
+ idx = 5;
+ } else {
+ hash = g_str_hash (name);
+ number_of_colors = G_N_ELEMENTS (gnome_color_palette);
+ idx = hash % number_of_colors;
+ }
+
+ color.red = gnome_color_palette[idx][0];
+ color.green = gnome_color_palette[idx][1];
+ color.blue = gnome_color_palette[idx][2];
+
+ return color;
+}
+
+static void
+darken_color (GdkRGBA *color, gdouble fraction) {
+ color->red = CLAMP (color->red + color->red * fraction, 0, 255);
+ color->green = CLAMP (color->green + color->green * fraction, 0, 255);
+ color->blue = CLAMP (color->blue + color->blue * fraction, 0, 255);
+}
+
+cairo_surface_t *
+generate_user_picture (const gchar *name) {
+ PangoFontDescription *font_desc;
+ g_autofree gchar *initials = extract_initials_from_name (name);
+ g_autofree gchar *font = g_strdup_printf ("Sans %d", (int)ceil (IMAGE_SIZE / 2.5));
+ PangoLayout *layout;
+ GdkRGBA color = get_color_for_name (name);
+ cairo_surface_t *surface;
+ gint width, height;
+ cairo_t *cr;
+
+ surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
+ IMAGE_SIZE,
+ IMAGE_SIZE);
+ cr = cairo_create (surface);
+
+ cairo_arc (cr, IMAGE_SIZE/2, IMAGE_SIZE/2, IMAGE_SIZE/2, 0, 2 * G_PI);
+ cairo_set_source_rgb (cr, color.red/255.0, color.green/255.0, color.blue/255.0);
+ cairo_fill (cr);
+
+ cairo_arc (cr, IMAGE_SIZE / 2, IMAGE_SIZE / 2, IMAGE_SIZE / 2 - IMAGE_BORDER_WIDTH / 2,
+ 0, 2 * G_PI);
+ darken_color (&color, -0.3);
+ cairo_set_source_rgb (cr, color.red / 255.0, color.green / 255.0, color.blue / 255.0);
+ cairo_set_line_width (cr, IMAGE_BORDER_WIDTH);
+ cairo_stroke (cr);
+
+ /* Draw the initials on top */
+ cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
+ layout = pango_cairo_create_layout (cr);
+ pango_layout_set_text (layout, initials, -1);
+ font_desc = pango_font_description_from_string (font);
+ pango_layout_set_font_description (layout, font_desc);
+ pango_font_description_free (font_desc);
+
+ pango_layout_get_size (layout, &width, &height);
+ cairo_translate (cr, IMAGE_SIZE/2, IMAGE_SIZE/2);
+ cairo_move_to (cr, - ((double)width / PANGO_SCALE)/2, - ((double)height/PANGO_SCALE)/2);
+ pango_cairo_show_layout (cr, layout);
+ cairo_destroy (cr);
+
+ return surface;
+}
+
+GdkPixbuf *
+round_image (GdkPixbuf *image)
+{
+ GdkPixbuf *dest = NULL;
+ cairo_surface_t *surface;
+ cairo_t *cr;
+ gint size;
+
+ size = gdk_pixbuf_get_width (image);
+ surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, size, size);
+ cr = cairo_create (surface);
+
+ /* Clip a circle */
+ cairo_arc (cr, size/2, size/2, size/2, 0, 2 * G_PI);
+ cairo_clip (cr);
+ cairo_new_path (cr);
+
+ gdk_cairo_set_source_pixbuf (cr, image, 0, 0);
+ cairo_paint (cr);
+
+ dest = gdk_pixbuf_get_from_surface (surface, 0, 0, size, size);
+ cairo_surface_destroy (surface);
+ cairo_destroy (cr);
+
+ return dest;
+}
diff --git a/gnome-initial-setup/pages/account/um-utils.h b/gnome-initial-setup/pages/account/um-utils.h
new file mode 100644
index 0000000..8a8c7cd
--- /dev/null
+++ b/gnome-initial-setup/pages/account/um-utils.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2009-2010 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA
+ *
+ * Written by: Matthias Clasen <mclasen@redhat.com>
+ */
+
+#ifndef __UM_UTILS_H__
+#define __UM_UTILS_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+void set_entry_validation_error (GtkEntry *entry,
+ const gchar *text);
+void set_entry_validation_checkmark (GtkEntry *entry);
+void clear_entry_validation_error (GtkEntry *entry);
+
+gboolean is_valid_name (const gchar *name);
+gboolean is_valid_username (const gchar *name,
+ gboolean parental_controls_enabled,
+ gchar **tip);
+
+void generate_username_choices (const gchar *name,
+ GtkListStore *store);
+
+cairo_surface_t *generate_user_picture (const gchar *name);
+
+GdkPixbuf *round_image (GdkPixbuf *image);
+
+
+G_END_DECLS
+
+#endif