1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
2 *
3 * Copyright 2009-2010 Red Hat, Inc,
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, see <http://www.gnu.org/licenses/>.
17 *
18 * Written by: Matthias Clasen <mclasen@redhat.com>
19 */
20
21 #include "config.h"
22
23 #include <unistd.h>
24 #include <stdlib.h>
25 #include <sys/types.h>
26 #include <sys/wait.h>
27
28 #include <glib.h>
29 #include <glib/gi18n.h>
30 #include <gtk/gtk.h>
31 #include <act/act.h>
32
33 #include "cc-password-dialog.h"
34 #include "cc-user-accounts-resources.h"
35 #include "pw-utils.h"
36 #include "run-passwd.h"
37 #include "user-utils.h"
38
39 #define PASSWORD_CHECK_TIMEOUT 600
40
41 struct _CcPasswordDialog
42 {
43 GtkDialog parent_instance;
44
45 GtkBox *action_radio_box;
46 GtkRadioButton *action_now_radio;
47 GtkRadioButton *action_login_radio;
48 GtkButton *ok_button;
49 GtkLabel *old_password_label;
50 GtkEntry *old_password_entry;
51 GtkEntry *password_entry;
52 GtkLabel *password_hint_label;
53 GtkLevelBar *strength_indicator;
54 GtkEntry *verify_entry;
55 GtkLabel *verify_hint_label;
56
57 gint password_entry_timeout_id;
58
59 ActUser *user;
60 ActUserPasswordMode password_mode;
61
62 gboolean old_password_ok;
63 gint old_password_entry_timeout_id;
64
65 PasswdHandler *passwd_handler;
66 };
67
G_DEFINE_TYPE(CcPasswordDialog,cc_password_dialog,GTK_TYPE_DIALOG)68 G_DEFINE_TYPE (CcPasswordDialog, cc_password_dialog, GTK_TYPE_DIALOG)
69
70 static gint
71 update_password_strength (CcPasswordDialog *self)
72 {
73 const gchar *password;
74 const gchar *old_password;
75 const gchar *username;
76 gint strength_level;
77 const gchar *hint;
78 const gchar *verify;
79
80 password = gtk_entry_get_text (self->password_entry);
81 old_password = gtk_entry_get_text (self->old_password_entry);
82 username = act_user_get_user_name (self->user);
83
84 pw_strength (password, old_password, username,
85 &hint, &strength_level);
86
87 gtk_level_bar_set_value (self->strength_indicator, strength_level);
88 gtk_label_set_label (self->password_hint_label, hint);
89
90 if (strength_level > 1) {
91 set_entry_validation_checkmark (self->password_entry);
92 } else if (strlen (password) == 0) {
93 set_entry_generation_icon (self->password_entry);
94 } else {
95 clear_entry_validation_error (self->password_entry);
96 }
97
98 verify = gtk_entry_get_text (self->verify_entry);
99 if (strlen (verify) == 0) {
100 gtk_widget_set_sensitive (GTK_WIDGET (self->verify_entry), strength_level > 1);
101 }
102
103 return strength_level;
104 }
105
106 static void
password_changed_cb(PasswdHandler * handler,GError * error,CcPasswordDialog * self)107 password_changed_cb (PasswdHandler *handler,
108 GError *error,
109 CcPasswordDialog *self)
110 {
111 GtkWidget *dialog;
112 const gchar *primary_text;
113 const gchar *secondary_text;
114
115 gtk_widget_set_sensitive (GTK_WIDGET (self), TRUE);
116 gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (self)), NULL);
117
118 if (!error) {
119 gtk_dialog_response (GTK_DIALOG (self), GTK_RESPONSE_ACCEPT);
120 return;
121 }
122
123 if (error->code == PASSWD_ERROR_REJECTED) {
124 primary_text = error->message;
125 secondary_text = _("Please choose another password.");
126
127 gtk_entry_set_text (self->password_entry, "");
128 gtk_widget_grab_focus (GTK_WIDGET (self->password_entry));
129
130 gtk_entry_set_text (self->verify_entry, "");
131 }
132 else if (error->code == PASSWD_ERROR_AUTH_FAILED) {
133 primary_text = error->message;
134 secondary_text = _("Please type your current password again.");
135
136 gtk_entry_set_text (self->old_password_entry, "");
137 gtk_widget_grab_focus (GTK_WIDGET (self->old_password_entry));
138 }
139 else {
140 primary_text = _("Password could not be changed");
141 secondary_text = error->message;
142 }
143
144 dialog = gtk_message_dialog_new (GTK_WINDOW (self),
145 GTK_DIALOG_MODAL,
146 GTK_MESSAGE_ERROR,
147 GTK_BUTTONS_CLOSE,
148 "%s", primary_text);
149 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
150 "%s", secondary_text);
151 gtk_dialog_run (GTK_DIALOG (dialog));
152 gtk_widget_destroy (dialog);
153 }
154
155 static void
ok_button_clicked_cb(CcPasswordDialog * self)156 ok_button_clicked_cb (CcPasswordDialog *self)
157 {
158 const gchar *password;
159
160 password = gtk_entry_get_text (self->password_entry);
161
162 switch (self->password_mode) {
163 case ACT_USER_PASSWORD_MODE_REGULAR:
164 if (act_user_get_uid (self->user) == getuid ()) {
165 GdkDisplay *display;
166 g_autoptr(GdkCursor) cursor = NULL;
167
168 /* When setting a password for the current user,
169 * use passwd directly, to preserve the audit trail
170 * and to e.g. update the keyring password.
171 */
172 passwd_change_password (self->passwd_handler, password,
173 (PasswdCallback) password_changed_cb, self);
174 gtk_widget_set_sensitive (GTK_WIDGET (self), FALSE);
175 display = gtk_widget_get_display (GTK_WIDGET (self));
176 cursor = gdk_cursor_new_for_display (display, GDK_WATCH);
177 gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (self)), cursor);
178 gdk_display_flush (display);
179 return;
180 }
181
182 act_user_set_password_mode (self->user, ACT_USER_PASSWORD_MODE_REGULAR);
183 act_user_set_password (self->user, password, "");
184 break;
185
186 case ACT_USER_PASSWORD_MODE_SET_AT_LOGIN:
187 act_user_set_password_mode (self->user, self->password_mode);
188 act_user_set_automatic_login (self->user, FALSE);
189 break;
190
191 default:
192 g_assert_not_reached ();
193 }
194
195 gtk_dialog_response (GTK_DIALOG (self), GTK_RESPONSE_ACCEPT);
196 }
197
198 static void
update_sensitivity(CcPasswordDialog * self)199 update_sensitivity (CcPasswordDialog *self)
200 {
201 const gchar *password, *verify;
202 gboolean can_change;
203 int strength;
204
205 password = gtk_entry_get_text (self->password_entry);
206 verify = gtk_entry_get_text (self->verify_entry);
207
208 if (self->password_mode == ACT_USER_PASSWORD_MODE_REGULAR) {
209 strength = update_password_strength (self);
210 can_change = strength > 1 && strcmp (password, verify) == 0 &&
211 (self->old_password_ok || !gtk_widget_get_visible (GTK_WIDGET (self->old_password_entry)));
212 }
213 else {
214 can_change = TRUE;
215 }
216
217 gtk_widget_set_sensitive (GTK_WIDGET (self->ok_button), can_change);
218 }
219
220 static void
mode_change(CcPasswordDialog * self,ActUserPasswordMode mode)221 mode_change (CcPasswordDialog *self,
222 ActUserPasswordMode mode)
223 {
224 gboolean active;
225
226 active = (mode == ACT_USER_PASSWORD_MODE_REGULAR);
227 gtk_widget_set_sensitive (GTK_WIDGET (self->password_entry), active);
228 gtk_widget_set_sensitive (GTK_WIDGET (self->verify_entry), active);
229 gtk_widget_set_sensitive (GTK_WIDGET (self->old_password_entry), active);
230 gtk_widget_set_sensitive (GTK_WIDGET (self->password_hint_label), active);
231 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->action_now_radio), active);
232 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->action_login_radio), !active);
233
234 self->password_mode = mode;
235 update_sensitivity (self);
236 }
237
238 static void
action_now_radio_toggled_cb(CcPasswordDialog * self)239 action_now_radio_toggled_cb (CcPasswordDialog *self)
240 {
241 gint active;
242 ActUserPasswordMode mode;
243
244 active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->action_now_radio));
245 mode = active ? ACT_USER_PASSWORD_MODE_REGULAR : ACT_USER_PASSWORD_MODE_SET_AT_LOGIN;
246 mode_change (self, mode);
247 }
248
249 static void
update_password_match(CcPasswordDialog * self)250 update_password_match (CcPasswordDialog *self)
251 {
252 const gchar *password;
253 const gchar *verify;
254 const gchar *message = "";
255
256 password = gtk_entry_get_text (self->password_entry);
257 verify = gtk_entry_get_text (self->verify_entry);
258
259 if (strlen (verify) > 0) {
260 if (strcmp (password, verify) != 0) {
261 message = _("The passwords do not match.");
262 }
263 else {
264 set_entry_validation_checkmark (self->verify_entry);
265 }
266 }
267 gtk_label_set_label (self->verify_hint_label, message);
268 }
269
270 static gboolean
password_entry_timeout(CcPasswordDialog * self)271 password_entry_timeout (CcPasswordDialog *self)
272 {
273 update_password_strength (self);
274 update_sensitivity (self);
275 update_password_match (self);
276
277 self->password_entry_timeout_id = 0;
278
279 return FALSE;
280 }
281
282 static void
recheck_password_match(CcPasswordDialog * self)283 recheck_password_match (CcPasswordDialog *self)
284 {
285 const gchar *password;
286
287 if (self->password_entry_timeout_id != 0) {
288 g_source_remove (self->password_entry_timeout_id);
289 self->password_entry_timeout_id = 0;
290 }
291
292 gtk_widget_set_sensitive (GTK_WIDGET (self->ok_button), FALSE);
293
294 password = gtk_entry_get_text (self->password_entry);
295 if (strlen (password) == 0) {
296 gtk_entry_set_visibility (self->password_entry, FALSE);
297 }
298
299 self->password_entry_timeout_id = g_timeout_add (PASSWORD_CHECK_TIMEOUT,
300 (GSourceFunc) password_entry_timeout,
301 self);
302 }
303
304 static void
password_entry_changed(CcPasswordDialog * self)305 password_entry_changed (CcPasswordDialog *self)
306 {
307 clear_entry_validation_error (self->password_entry);
308 clear_entry_validation_error (self->verify_entry);
309 recheck_password_match (self);
310 }
311
312 static void
verify_entry_changed(CcPasswordDialog * self)313 verify_entry_changed (CcPasswordDialog *self)
314 {
315 clear_entry_validation_error (self->verify_entry);
316 recheck_password_match (self);
317 }
318
319 static gboolean
password_entry_focus_out_cb(CcPasswordDialog * self)320 password_entry_focus_out_cb (CcPasswordDialog *self)
321 {
322 if (self->password_entry_timeout_id != 0) {
323 g_source_remove (self->password_entry_timeout_id);
324 self->password_entry_timeout_id = 0;
325 }
326
327 if (self->user != NULL)
328 password_entry_timeout (self);
329
330 return FALSE;
331 }
332
333 static gboolean
password_entry_key_press_cb(CcPasswordDialog * self,GdkEvent * event)334 password_entry_key_press_cb (CcPasswordDialog *self,
335 GdkEvent *event)
336 {
337 GdkEventKey *key = (GdkEventKey *)event;
338
339 if (self->password_entry_timeout_id != 0) {
340 g_source_remove (self->password_entry_timeout_id);
341 self->password_entry_timeout_id = 0;
342 }
343
344 if (key->keyval == GDK_KEY_Tab)
345 password_entry_timeout (self);
346
347 return FALSE;
348 }
349
350 static void
auth_cb(PasswdHandler * handler,GError * error,CcPasswordDialog * self)351 auth_cb (PasswdHandler *handler,
352 GError *error,
353 CcPasswordDialog *self)
354 {
355 if (error) {
356 self->old_password_ok = FALSE;
357 }
358 else {
359 self->old_password_ok = TRUE;
360 set_entry_validation_checkmark (self->old_password_entry);
361 }
362
363 update_sensitivity (self);
364 }
365
366 static gboolean
old_password_entry_timeout(CcPasswordDialog * self)367 old_password_entry_timeout (CcPasswordDialog *self)
368 {
369 const gchar *text;
370
371 update_sensitivity (self);
372
373 text = gtk_entry_get_text (self->old_password_entry);
374 if (!self->old_password_ok) {
375 passwd_authenticate (self->passwd_handler, text, (PasswdCallback)auth_cb, self);
376 }
377
378 self->old_password_entry_timeout_id = 0;
379
380 return FALSE;
381 }
382
383 static gboolean
old_password_entry_focus_out_cb(CcPasswordDialog * self)384 old_password_entry_focus_out_cb (CcPasswordDialog *self)
385 {
386 if (self->old_password_entry_timeout_id != 0) {
387 g_source_remove (self->old_password_entry_timeout_id);
388 self->old_password_entry_timeout_id = 0;
389 }
390
391 if (self->user != NULL)
392 old_password_entry_timeout (self);
393
394 return FALSE;
395 }
396
397 static void
old_password_entry_changed(CcPasswordDialog * self)398 old_password_entry_changed (CcPasswordDialog *self)
399 {
400 if (self->old_password_entry_timeout_id != 0) {
401 g_source_remove (self->old_password_entry_timeout_id);
402 self->old_password_entry_timeout_id = 0;
403 }
404
405 clear_entry_validation_error (self->old_password_entry);
406 gtk_widget_set_sensitive (GTK_WIDGET (self->ok_button), FALSE);
407
408 self->old_password_ok = FALSE;
409 self->old_password_entry_timeout_id = g_timeout_add (PASSWORD_CHECK_TIMEOUT,
410 (GSourceFunc) old_password_entry_timeout,
411 self);
412 }
413
414 static void
password_entry_icon_press_cb(CcPasswordDialog * self)415 password_entry_icon_press_cb (CcPasswordDialog *self)
416 {
417 g_autofree gchar *pwd = NULL;
418
419 pwd = pw_generate ();
420 if (pwd == NULL)
421 return;
422
423 gtk_entry_set_text (self->password_entry, pwd);
424 gtk_entry_set_text (self->verify_entry, pwd);
425 gtk_entry_set_visibility (self->password_entry, TRUE);
426 gtk_widget_set_sensitive (GTK_WIDGET (self->verify_entry), TRUE);
427 }
428
429 static void
cc_password_dialog_dispose(GObject * object)430 cc_password_dialog_dispose (GObject *object)
431 {
432 CcPasswordDialog *self = CC_PASSWORD_DIALOG (object);
433
434 g_clear_object (&self->user);
435
436 if (self->passwd_handler) {
437 passwd_destroy (self->passwd_handler);
438 self->passwd_handler = NULL;
439 }
440
441 if (self->old_password_entry_timeout_id != 0) {
442 g_source_remove (self->old_password_entry_timeout_id);
443 self->old_password_entry_timeout_id = 0;
444 }
445
446 if (self->password_entry_timeout_id != 0) {
447 g_source_remove (self->password_entry_timeout_id);
448 self->password_entry_timeout_id = 0;
449 }
450
451 G_OBJECT_CLASS (cc_password_dialog_parent_class)->dispose (object);
452 }
453
454 static void
cc_password_dialog_class_init(CcPasswordDialogClass * klass)455 cc_password_dialog_class_init (CcPasswordDialogClass *klass)
456 {
457 GObjectClass *object_class = G_OBJECT_CLASS (klass);
458 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
459
460 object_class->dispose = cc_password_dialog_dispose;
461
462 gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/user-accounts/cc-password-dialog.ui");
463
464 gtk_widget_class_bind_template_child (widget_class, CcPasswordDialog, action_radio_box);
465 gtk_widget_class_bind_template_child (widget_class, CcPasswordDialog, action_now_radio);
466 gtk_widget_class_bind_template_child (widget_class, CcPasswordDialog, action_login_radio);
467 gtk_widget_class_bind_template_child (widget_class, CcPasswordDialog, ok_button);
468 gtk_widget_class_bind_template_child (widget_class, CcPasswordDialog, old_password_label);
469 gtk_widget_class_bind_template_child (widget_class, CcPasswordDialog, old_password_entry);
470 gtk_widget_class_bind_template_child (widget_class, CcPasswordDialog, password_entry);
471 gtk_widget_class_bind_template_child (widget_class, CcPasswordDialog, password_hint_label);
472 gtk_widget_class_bind_template_child (widget_class, CcPasswordDialog, strength_indicator);
473 gtk_widget_class_bind_template_child (widget_class, CcPasswordDialog, verify_entry);
474 gtk_widget_class_bind_template_child (widget_class, CcPasswordDialog, verify_hint_label);
475
476 gtk_widget_class_bind_template_callback (widget_class, action_now_radio_toggled_cb);
477 gtk_widget_class_bind_template_callback (widget_class, old_password_entry_changed);
478 gtk_widget_class_bind_template_callback (widget_class, old_password_entry_focus_out_cb);
479 gtk_widget_class_bind_template_callback (widget_class, ok_button_clicked_cb);
480 gtk_widget_class_bind_template_callback (widget_class, password_entry_changed);
481 gtk_widget_class_bind_template_callback (widget_class, password_entry_focus_out_cb);
482 gtk_widget_class_bind_template_callback (widget_class, password_entry_icon_press_cb);
483 gtk_widget_class_bind_template_callback (widget_class, password_entry_key_press_cb);
484 gtk_widget_class_bind_template_callback (widget_class, verify_entry_changed);
485 }
486
487 static void
cc_password_dialog_init(CcPasswordDialog * self)488 cc_password_dialog_init (CcPasswordDialog *self)
489 {
490 g_resources_register (cc_user_accounts_get_resource ());
491
492 gtk_widget_init_template (GTK_WIDGET (self));
493 }
494
495 CcPasswordDialog *
cc_password_dialog_new(ActUser * user)496 cc_password_dialog_new (ActUser *user)
497 {
498 CcPasswordDialog *self;
499
500 g_return_val_if_fail (ACT_IS_USER (user), NULL);
501
502 self = g_object_new (CC_TYPE_PASSWORD_DIALOG,
503 "use-header-bar", 1,
504 NULL);
505
506 self->user = g_object_ref (user);
507
508 if (act_user_get_uid (self->user) == getuid ()) {
509 gboolean visible;
510
511 mode_change (self, ACT_USER_PASSWORD_MODE_REGULAR);
512 gtk_widget_hide (GTK_WIDGET (self->action_radio_box));
513
514 visible = (act_user_get_password_mode (user) != ACT_USER_PASSWORD_MODE_NONE);
515 gtk_widget_set_visible (GTK_WIDGET (self->old_password_label), visible);
516 gtk_widget_set_visible (GTK_WIDGET (self->old_password_entry), visible);
517 self->old_password_ok = !visible;
518
519 self->passwd_handler = passwd_init ();
520 }
521 else {
522 mode_change (self, ACT_USER_PASSWORD_MODE_SET_AT_LOGIN);
523 gtk_widget_show (GTK_WIDGET (self->action_radio_box));
524
525 gtk_widget_hide (GTK_WIDGET (self->old_password_label));
526 gtk_widget_hide (GTK_WIDGET (self->old_password_entry));
527 self->old_password_ok = TRUE;
528 }
529
530 if (self->old_password_ok == FALSE)
531 gtk_widget_grab_focus (GTK_WIDGET (self->old_password_entry));
532 else
533 gtk_widget_grab_focus (GTK_WIDGET (self->password_entry));
534
535 gtk_widget_grab_default (GTK_WIDGET (self->ok_button));
536
537 return self;
538 }
539