/* -*- 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 . * * Written by: * Jasper St. Pierre */ /* Password page {{{1 */ #define PAGE_ID "password" #include "config.h" #include "password-resources.h" #include "gis-password-page.h" #include "gis-keyring.h" #include "pw-utils.h" #include #include #include "gis-page-header.h" #define VALIDATION_TIMEOUT 600 struct _GisPasswordPagePrivate { GtkWidget *password_entry; GtkWidget *confirm_entry; GtkWidget *password_strength; GtkWidget *password_explanation; GtkWidget *confirm_explanation; GtkWidget *header; gboolean valid_confirm; gboolean valid_password; guint timeout_id; const gchar *username; gboolean parent_mode; }; typedef struct _GisPasswordPagePrivate GisPasswordPagePrivate; G_DEFINE_TYPE_WITH_PRIVATE (GisPasswordPage, gis_password_page, GIS_TYPE_PAGE); typedef enum { PROP_PARENT_MODE = 1, } GisPasswordPageProperty; static GParamSpec *obj_props[PROP_PARENT_MODE + 1]; static void clear_password_validation_error (GtkWidget *entry) { gtk_widget_remove_css_class (entry, "error"); } static void set_password_validation_error (GtkWidget *entry) { gtk_widget_add_css_class (entry, "error"); } static void update_header (GisPasswordPage *page) { GisPasswordPagePrivate *priv = gis_password_page_get_instance_private (page); g_autofree gchar *title = NULL; g_autofree gchar *subtitle = NULL; const gchar *icon_name; GdkPaintable *paintable; #ifndef HAVE_PARENTAL_CONTROLS /* Don’t break UI compatibility if parental controls are disabled. */ title = g_strdup (_("Set a Password")); subtitle = g_strdup (_("Be careful not to lose your password.")); paintable = NULL; icon_name = "dialog-password-symbolic"; #else if (!priv->parent_mode) { /* Translators: The placeholder is for the user’s full name. */ title = g_strdup_printf (_("Set a Password for %s"), gis_driver_get_full_name (GIS_PAGE (page)->driver)); subtitle = g_strdup (_("Be careful not to lose your password.")); paintable = gis_driver_get_avatar (GIS_PAGE (page)->driver); icon_name = (paintable != NULL) ? NULL : "dialog-password-symbolic"; } else { title = g_strdup (_("Set a Parent Password")); /* Translators: The placeholder is the full name of the child user on the system. */ subtitle = g_strdup_printf (_("This password will control access to the parental controls for %s."), gis_driver_get_full_name (GIS_PAGE (page)->driver)); icon_name = "org.freedesktop.MalcontentControl-symbolic"; paintable = NULL; } #endif /* Doesn’t make sense to set both. */ g_assert (icon_name == NULL || paintable == NULL); g_object_set (G_OBJECT (priv->header), "title", title, "subtitle", subtitle, NULL); if (paintable != NULL) g_object_set (G_OBJECT (priv->header), "paintable", paintable, NULL); else if (icon_name != NULL) g_object_set (G_OBJECT (priv->header), "icon-name", icon_name, NULL); } static void set_parent_mode (GisPasswordPage *page, gboolean parent_mode) { GisPasswordPagePrivate *priv = gis_password_page_get_instance_private (page); g_return_if_fail (GIS_IS_PASSWORD_PAGE (page)); if (priv->parent_mode == parent_mode) return; priv->parent_mode = parent_mode; g_object_notify_by_pspec (G_OBJECT (page), obj_props[PROP_PARENT_MODE]); update_header (page); } static gboolean page_validate (GisPasswordPage *page) { GisPasswordPagePrivate *priv = gis_password_page_get_instance_private (page); return priv->valid_confirm; } static void update_page_validation (GisPasswordPage *page) { gis_page_set_complete (GIS_PAGE (page), page_validate (page)); } static gboolean gis_password_page_save_data (GisPage *gis_page, GError **error) { GisPasswordPage *page = GIS_PASSWORD_PAGE (gis_page); GisPasswordPagePrivate *priv = gis_password_page_get_instance_private (page); ActUser *act_user; UmAccountMode account_mode; const gchar *password = NULL; g_assert (gis_page->driver != NULL); account_mode = gis_driver_get_account_mode (gis_page->driver); if (!priv->parent_mode) gis_driver_get_user_permissions (gis_page->driver, &act_user, &password); else gis_driver_get_parent_permissions (gis_page->driver, &act_user, &password); if (account_mode == UM_ENTERPRISE) { g_assert (!priv->parent_mode); if (password != NULL) gis_update_login_keyring_password (password); return TRUE; } password = gtk_editable_get_text (GTK_EDITABLE (priv->password_entry)); if (strlen (password) == 0) act_user_set_password_mode (act_user, ACT_USER_PASSWORD_MODE_NONE); else act_user_set_password (act_user, password, ""); if (!priv->parent_mode) gis_driver_set_user_permissions (gis_page->driver, act_user, password); else gis_driver_set_parent_permissions (gis_page->driver, act_user, password); if (!priv->parent_mode) gis_update_login_keyring_password (password); return TRUE; } static void gis_password_page_shown (GisPage *gis_page) { GisPasswordPage *page = GIS_PASSWORD_PAGE (gis_page); GisPasswordPagePrivate *priv = gis_password_page_get_instance_private (page); gtk_widget_grab_focus (priv->password_entry); } static gboolean validate (GisPasswordPage *page) { GisPasswordPagePrivate *priv = gis_password_page_get_instance_private (page); const gchar *password; const gchar *verify; gint strength_level; const gchar *hint; g_clear_handle_id (&priv->timeout_id, g_source_remove); password = gtk_editable_get_text (GTK_EDITABLE (priv->password_entry)); verify = gtk_editable_get_text (GTK_EDITABLE (priv->confirm_entry)); pw_strength (password, NULL, priv->username, &hint, &strength_level); gtk_level_bar_set_value (GTK_LEVEL_BAR (priv->password_strength), strength_level); gtk_label_set_label (GTK_LABEL (priv->password_explanation), hint); gtk_label_set_label (GTK_LABEL (priv->confirm_explanation), ""); priv->valid_confirm = FALSE; priv->valid_password = (strlen (password) && strength_level > 1); if (priv->valid_password) clear_password_validation_error (priv->password_entry); else set_password_validation_error (priv->password_entry); if (strlen (password) > 0 && strlen (verify) > 0) { priv->valid_confirm = (strcmp (password, verify) == 0); if (!priv->valid_confirm) gtk_label_set_label (GTK_LABEL (priv->confirm_explanation), _("The passwords do not match.")); else clear_password_validation_error (priv->password_entry); } /* * We deliberately don’t validate that the parent password and main user * password are different. It’s more feasible that someone would usefully * want to set their system up that way, than it is that the parent and child * would accidentally choose the same password. */ update_page_validation (page); return G_SOURCE_REMOVE; } static gboolean on_focusout (GisPasswordPage *page) { validate (page); return FALSE; } static void password_changed (GtkWidget *w, GParamSpec *pspec, GisPasswordPage *page) { GisPasswordPagePrivate *priv = gis_password_page_get_instance_private (page); clear_password_validation_error (w); clear_password_validation_error (priv->confirm_entry); priv->valid_password = FALSE; update_page_validation (page); if (priv->timeout_id != 0) g_source_remove (priv->timeout_id); priv->timeout_id = g_timeout_add (VALIDATION_TIMEOUT, (GSourceFunc)validate, page); } static void confirm_changed (GtkWidget *w, GParamSpec *pspec, GisPasswordPage *page) { GisPasswordPagePrivate *priv = gis_password_page_get_instance_private (page); clear_password_validation_error (w); priv->valid_confirm = FALSE; update_page_validation (page); if (priv->timeout_id != 0) g_source_remove (priv->timeout_id); priv->timeout_id = g_timeout_add (VALIDATION_TIMEOUT, (GSourceFunc)validate, page); } static void username_changed (GObject *obj, GParamSpec *pspec, GisPasswordPage *page) { GisPasswordPagePrivate *priv = gis_password_page_get_instance_private (page); priv->username = gis_driver_get_username (GIS_DRIVER (obj)); if (priv->username) gtk_widget_show (GTK_WIDGET (page)); else gtk_widget_hide (GTK_WIDGET (page)); clear_password_validation_error (priv->password_entry); clear_password_validation_error (priv->confirm_entry); validate (page); } static void full_name_or_avatar_changed (GObject *obj, GParamSpec *pspec, gpointer user_data) { GisPasswordPage *page = GIS_PASSWORD_PAGE (user_data); update_header (page); } static void confirm (GisPasswordPage *page) { if (page_validate (page)) gis_assistant_next_page (gis_driver_get_assistant (GIS_PAGE (page)->driver)); } static void track_focus_out (GisPasswordPage *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_password_page_constructed (GObject *object) { GisPasswordPage *page = GIS_PASSWORD_PAGE (object); GisPasswordPagePrivate *priv = gis_password_page_get_instance_private (page); G_OBJECT_CLASS (gis_password_page_parent_class)->constructed (object); g_signal_connect (priv->password_entry, "notify::text", G_CALLBACK (password_changed), page); g_signal_connect_swapped (priv->password_entry, "activate", G_CALLBACK (confirm), page); track_focus_out (page, priv->password_entry); g_signal_connect (priv->confirm_entry, "notify::text", G_CALLBACK (confirm_changed), page); g_signal_connect_swapped (priv->confirm_entry, "activate", G_CALLBACK (confirm), page); track_focus_out (page, priv->confirm_entry); g_signal_connect (GIS_PAGE (page)->driver, "notify::username", G_CALLBACK (username_changed), page); g_signal_connect (GIS_PAGE (page)->driver, "notify::full-name", G_CALLBACK (full_name_or_avatar_changed), page); g_signal_connect (GIS_PAGE (page)->driver, "notify::avatar", G_CALLBACK (full_name_or_avatar_changed), page); validate (page); update_header (page); gtk_widget_show (GTK_WIDGET (page)); } static void gis_password_page_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GisPasswordPage *page = GIS_PASSWORD_PAGE (object); GisPasswordPagePrivate *priv = gis_password_page_get_instance_private (page); switch ((GisPasswordPageProperty) prop_id) { case PROP_PARENT_MODE: g_value_set_boolean (value, priv->parent_mode); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gis_password_page_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GisPasswordPage *page = GIS_PASSWORD_PAGE (object); switch ((GisPasswordPageProperty) prop_id) { case PROP_PARENT_MODE: set_parent_mode (page, g_value_get_boolean (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gis_password_page_dispose (GObject *object) { if (GIS_PAGE (object)->driver) { g_signal_handlers_disconnect_by_func (GIS_PAGE (object)->driver, username_changed, object); g_signal_handlers_disconnect_by_func (GIS_PAGE (object)->driver, full_name_or_avatar_changed, object); } G_OBJECT_CLASS (gis_password_page_parent_class)->dispose (object); } static void gis_password_page_locale_changed (GisPage *page) { gis_page_set_title (GIS_PAGE (page), _("Password")); } static void gis_password_page_class_init (GisPasswordPageClass *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-password-page.ui"); gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisPasswordPage, password_entry); gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisPasswordPage, confirm_entry); gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisPasswordPage, password_strength); gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisPasswordPage, password_explanation); gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisPasswordPage, confirm_explanation); gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisPasswordPage, header); page_class->page_id = PAGE_ID; page_class->locale_changed = gis_password_page_locale_changed; page_class->save_data = gis_password_page_save_data; page_class->shown = gis_password_page_shown; object_class->constructed = gis_password_page_constructed; object_class->get_property = gis_password_page_get_property; object_class->set_property = gis_password_page_set_property; object_class->dispose = gis_password_page_dispose; /** * GisPasswordPage:parent-mode: * * If %FALSE (the default), this page will collect a password for the main * user account. If %TRUE, it will collect a password for controlling access * to parental controls — this will affect where the password is stored, and * the appearance of the page. * * Since: 3.36 */ obj_props[PROP_PARENT_MODE] = g_param_spec_boolean ("parent-mode", "Parent Mode", "Whether to collect a password for the main user account or a parent account.", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); g_object_class_install_properties (object_class, G_N_ELEMENTS (obj_props), obj_props); } static void gis_password_page_init (GisPasswordPage *page) { GtkCssProvider *provider; g_resources_register (password_get_resource ()); g_type_ensure (GIS_TYPE_PAGE_HEADER); provider = gtk_css_provider_new (); gtk_css_provider_load_from_resource (provider, "/org/gnome/initial-setup/gis-password-page.css"); gtk_style_context_add_provider_for_display (gdk_display_get_default (), GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); g_object_unref (provider); gtk_widget_init_template (GTK_WIDGET (page)); } GisPage * gis_prepare_password_page (GisDriver *driver) { return g_object_new (GIS_TYPE_PASSWORD_PAGE, "driver", driver, NULL); } GisPage * gis_prepare_parent_password_page (GisDriver *driver) { /* Skip prompting for the parent password if parental controls aren’t enabled. */ if (!gis_driver_get_parental_controls_enabled (driver)) return NULL; return g_object_new (GIS_TYPE_PASSWORD_PAGE, "driver", driver, "parent-mode", TRUE, NULL); }