~darkxst/ubuntu/quantal/gnome-shell/lp1128804

« back to all changes in this revision

Viewing changes to js/ui/components/polkitAgent.js

  • Committer: Package Import Robot
  • Author(s): Tim Lunn
  • Date: 2012-10-09 20:42:33 UTC
  • mfrom: (57.1.7 quantal)
  • Revision ID: package-import@ubuntu.com-20121009204233-chcl8989muuzfpws
Tags: 3.6.0-0ubuntu3
* debian/patches/ubuntu-lightdm-user-switching.patch
  - Fix user switching when running lightdm.  LP: #1064269
 

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
 
2
 
 
3
const Lang = imports.lang;
 
4
const Signals = imports.signals;
 
5
const Shell = imports.gi.Shell;
 
6
const AccountsService = imports.gi.AccountsService;
 
7
const Clutter = imports.gi.Clutter;
 
8
const St = imports.gi.St;
 
9
const Pango = imports.gi.Pango;
 
10
const GLib = imports.gi.GLib;
 
11
const Gio = imports.gi.Gio;
 
12
const Mainloop = imports.mainloop;
 
13
const Polkit = imports.gi.Polkit;
 
14
const PolkitAgent = imports.gi.PolkitAgent;
 
15
 
 
16
const Components = imports.ui.components;
 
17
const ModalDialog = imports.ui.modalDialog;
 
18
const ShellEntry = imports.ui.shellEntry;
 
19
const UserMenu = imports.ui.userMenu;
 
20
 
 
21
const DIALOG_ICON_SIZE = 48;
 
22
 
 
23
const AuthenticationDialog = new Lang.Class({
 
24
    Name: 'AuthenticationDialog',
 
25
    Extends: ModalDialog.ModalDialog,
 
26
 
 
27
    _init: function(actionId, message, cookie, userNames) {
 
28
        this.parent({ styleClass: 'prompt-dialog' });
 
29
 
 
30
        this.actionId = actionId;
 
31
        this.message = message;
 
32
        this.userNames = userNames;
 
33
        this._wasDismissed = false;
 
34
        this._completed = false;
 
35
 
 
36
        let mainContentBox = new St.BoxLayout({ style_class: 'prompt-dialog-main-layout',
 
37
                                                vertical: false });
 
38
        this.contentLayout.add(mainContentBox,
 
39
                               { x_fill: true,
 
40
                                 y_fill: true });
 
41
 
 
42
        let icon = new St.Icon({ icon_name: 'dialog-password-symbolic' });
 
43
        mainContentBox.add(icon,
 
44
                           { x_fill:  true,
 
45
                             y_fill:  false,
 
46
                             x_align: St.Align.END,
 
47
                             y_align: St.Align.START });
 
48
 
 
49
        let messageBox = new St.BoxLayout({ style_class: 'prompt-dialog-message-layout',
 
50
                                            vertical: true });
 
51
        mainContentBox.add(messageBox,
 
52
                           { y_align: St.Align.START });
 
53
 
 
54
        this._subjectLabel = new St.Label({ style_class: 'prompt-dialog-headline',
 
55
                                            text: _("Authentication Required") });
 
56
 
 
57
        messageBox.add(this._subjectLabel,
 
58
                       { y_fill:  false,
 
59
                         y_align: St.Align.START });
 
60
 
 
61
        this._descriptionLabel = new St.Label({ style_class: 'prompt-dialog-description',
 
62
                                                text: message });
 
63
        this._descriptionLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
 
64
        this._descriptionLabel.clutter_text.line_wrap = true;
 
65
 
 
66
        messageBox.add(this._descriptionLabel,
 
67
                       { y_fill:  true,
 
68
                         y_align: St.Align.START });
 
69
 
 
70
        if (userNames.length > 1) {
 
71
            log('polkitAuthenticationAgent: Received ' + userNames.length +
 
72
                ' identities that can be used for authentication. Only ' +
 
73
                'considering one.');
 
74
        }
 
75
 
 
76
        let userName = GLib.get_user_name();
 
77
        if (userNames.indexOf(userName) < 0)
 
78
            userName = 'root';
 
79
        if (userNames.indexOf(userName) < 0)
 
80
            userName = userNames[0];
 
81
 
 
82
        this._user = AccountsService.UserManager.get_default().get_user(userName);
 
83
        let userRealName = this._user.get_real_name()
 
84
        this._userLoadedId = this._user.connect('notify::is_loaded',
 
85
                                                Lang.bind(this, this._onUserChanged));
 
86
        this._userChangedId = this._user.connect('changed',
 
87
                                                 Lang.bind(this, this._onUserChanged));
 
88
 
 
89
        // Special case 'root'
 
90
        let userIsRoot = false;
 
91
        if (userName == 'root') {
 
92
            userIsRoot = true;
 
93
            userRealName = _("Administrator");
 
94
        }
 
95
 
 
96
        if (userIsRoot) {
 
97
            let userLabel = new St.Label(({ style_class: 'polkit-dialog-user-root-label',
 
98
                                            text: userRealName }));
 
99
            messageBox.add(userLabel);
 
100
        } else {
 
101
            let userBox = new St.BoxLayout({ style_class: 'polkit-dialog-user-layout',
 
102
                                             vertical: false });
 
103
            messageBox.add(userBox);
 
104
            this._userAvatar = new UserMenu.UserAvatarWidget(this._user,
 
105
                                                             { iconSize: DIALOG_ICON_SIZE,
 
106
                                                               styleClass: 'polkit-dialog-user-icon' });
 
107
            this._userAvatar.actor.hide();
 
108
            userBox.add(this._userAvatar.actor,
 
109
                        { x_fill:  true,
 
110
                          y_fill:  false,
 
111
                          x_align: St.Align.END,
 
112
                          y_align: St.Align.START });
 
113
            let userLabel = new St.Label(({ style_class: 'polkit-dialog-user-label',
 
114
                                            text: userRealName }));
 
115
            userBox.add(userLabel,
 
116
                        { x_fill:  true,
 
117
                          y_fill:  false,
 
118
                          x_align: St.Align.END,
 
119
                          y_align: St.Align.MIDDLE });
 
120
        }
 
121
 
 
122
        this._onUserChanged();
 
123
 
 
124
        this._passwordBox = new St.BoxLayout({ vertical: false, style_class: 'prompt-dialog-password-box' });
 
125
        messageBox.add(this._passwordBox);
 
126
        this._passwordLabel = new St.Label(({ style_class: 'prompt-dialog-password-label' }));
 
127
        this._passwordBox.add(this._passwordLabel, { y_fill: false, y_align: St.Align.MIDDLE });
 
128
        this._passwordEntry = new St.Entry({ style_class: 'prompt-dialog-password-entry',
 
129
                                             text: "",
 
130
                                             can_focus: true});
 
131
        ShellEntry.addContextMenu(this._passwordEntry, { isPassword: true });
 
132
        this._passwordEntry.clutter_text.connect('activate', Lang.bind(this, this._onEntryActivate));
 
133
        this._passwordBox.add(this._passwordEntry,
 
134
                              { expand: true });
 
135
        this.setInitialKeyFocus(this._passwordEntry);
 
136
        this._passwordBox.hide();
 
137
 
 
138
        this._errorMessageLabel = new St.Label({ style_class: 'prompt-dialog-error-label' });
 
139
        this._errorMessageLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
 
140
        this._errorMessageLabel.clutter_text.line_wrap = true;
 
141
        messageBox.add(this._errorMessageLabel);
 
142
        this._errorMessageLabel.hide();
 
143
 
 
144
        this._infoMessageLabel = new St.Label({ style_class: 'prompt-dialog-info-label' });
 
145
        this._infoMessageLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
 
146
        this._infoMessageLabel.clutter_text.line_wrap = true;
 
147
        messageBox.add(this._infoMessageLabel);
 
148
        this._infoMessageLabel.hide();
 
149
 
 
150
        /* text is intentionally non-blank otherwise the height is not the same as for
 
151
         * infoMessage and errorMessageLabel - but it is still invisible because
 
152
         * gnome-shell.css sets the color to be transparent
 
153
         */
 
154
        this._nullMessageLabel = new St.Label({ style_class: 'prompt-dialog-null-label',
 
155
                                                text: 'abc'});
 
156
        this._nullMessageLabel.add_style_class_name('hidden');
 
157
        this._nullMessageLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
 
158
        this._nullMessageLabel.clutter_text.line_wrap = true;
 
159
        messageBox.add(this._nullMessageLabel);
 
160
        this._nullMessageLabel.show();
 
161
 
 
162
        this.setButtons([{ label: _("Cancel"),
 
163
                           action: Lang.bind(this, this.cancel),
 
164
                           key:    Clutter.Escape
 
165
                         },
 
166
                         { label:  _("Authenticate"),
 
167
                           action: Lang.bind(this, this._onAuthenticateButtonPressed),
 
168
                           default: true
 
169
                         }]);
 
170
 
 
171
        this._doneEmitted = false;
 
172
 
 
173
        this._identityToAuth = Polkit.UnixUser.new_for_name(userName);
 
174
        this._cookie = cookie;
 
175
 
 
176
        this._session = new PolkitAgent.Session({ identity: this._identityToAuth,
 
177
                                                  cookie: this._cookie });
 
178
        this._session.connect('completed', Lang.bind(this, this._onSessionCompleted));
 
179
        this._session.connect('request', Lang.bind(this, this._onSessionRequest));
 
180
        this._session.connect('show-error', Lang.bind(this, this._onSessionShowError));
 
181
        this._session.connect('show-info', Lang.bind(this, this._onSessionShowInfo));
 
182
    },
 
183
 
 
184
    startAuthentication: function() {
 
185
        this._session.initiate();
 
186
    },
 
187
 
 
188
    _ensureOpen: function() {
 
189
        // NOTE: ModalDialog.open() is safe to call if the dialog is
 
190
        // already open - it just returns true without side-effects
 
191
        if (!this.open(global.get_current_time())) {
 
192
            // This can fail if e.g. unable to get input grab
 
193
            //
 
194
            // In an ideal world this wouldn't happen (because the
 
195
            // Shell is in complete control of the session) but that's
 
196
            // just not how things work right now.
 
197
            //
 
198
            // One way to make this happen is by running 'sleep 3;
 
199
            // pkexec bash' and then opening a popup menu.
 
200
            //
 
201
            // We could add retrying if this turns out to be a problem
 
202
 
 
203
            log('polkitAuthenticationAgent: Failed to show modal dialog.' +
 
204
                ' Dismissing authentication request for action-id ' + this.actionId +
 
205
                ' cookie ' + this._cookie);
 
206
            this._emitDone(false, true);
 
207
        }
 
208
    },
 
209
 
 
210
    _emitDone: function(keepVisible, dismissed) {
 
211
        if (!this._doneEmitted) {
 
212
            this._doneEmitted = true;
 
213
            this.emit('done', keepVisible, dismissed);
 
214
        }
 
215
    },
 
216
 
 
217
    _onEntryActivate: function() {
 
218
        let response = this._passwordEntry.get_text();
 
219
        this._session.response(response);
 
220
        // When the user responds, dismiss already shown info and
 
221
        // error texts (if any)
 
222
        this._errorMessageLabel.hide();
 
223
        this._infoMessageLabel.hide();
 
224
        this._nullMessageLabel.show();
 
225
    },
 
226
 
 
227
    _onAuthenticateButtonPressed: function() {
 
228
        this._onEntryActivate();
 
229
    },
 
230
 
 
231
    _onSessionCompleted: function(session, gainedAuthorization) {
 
232
        if (this._completed)
 
233
            return;
 
234
 
 
235
        this._completed = true;
 
236
 
 
237
        if (!gainedAuthorization) {
 
238
            /* Unless we are showing an existing error message from the PAM
 
239
             * module (the PAM module could be reporting the authentication
 
240
             * error providing authentication-method specific information),
 
241
             * show "Sorry, that didn't work. Please try again."
 
242
             */
 
243
            if (!this._errorMessageLabel.visible && !this._wasDismissed) {
 
244
                /* Translators: "that didn't work" refers to the fact that the
 
245
                 * requested authentication was not gained; this can happen
 
246
                 * because of an authentication error (like invalid password),
 
247
                 * for instance. */
 
248
                this._errorMessageLabel.set_text(_("Sorry, that didn\'t work. Please try again."));
 
249
                this._errorMessageLabel.show();
 
250
                this._infoMessageLabel.hide();
 
251
                this._nullMessageLabel.hide();
 
252
            }
 
253
        }
 
254
        this._emitDone(!gainedAuthorization, false);
 
255
    },
 
256
 
 
257
    _onSessionRequest: function(session, request, echo_on) {
 
258
        // Cheap localization trick
 
259
        if (request == 'Password:' || request == 'Password: ')
 
260
            this._passwordLabel.set_text(_("Password:"));
 
261
        else
 
262
            this._passwordLabel.set_text(request);
 
263
 
 
264
        if (echo_on)
 
265
            this._passwordEntry.clutter_text.set_password_char('');
 
266
        else
 
267
            this._passwordEntry.clutter_text.set_password_char('\u25cf'); // ● U+25CF BLACK CIRCLE
 
268
 
 
269
        this._passwordBox.show();
 
270
        this._passwordEntry.set_text('');
 
271
        this._passwordEntry.grab_key_focus();
 
272
        this._ensureOpen();
 
273
    },
 
274
 
 
275
    _onSessionShowError: function(session, text) {
 
276
        this._passwordEntry.set_text('');
 
277
        this._errorMessageLabel.set_text(text);
 
278
        this._errorMessageLabel.show();
 
279
        this._infoMessageLabel.hide();
 
280
        this._nullMessageLabel.hide();
 
281
        this._ensureOpen();
 
282
    },
 
283
 
 
284
    _onSessionShowInfo: function(session, text) {
 
285
        this._passwordEntry.set_text('');
 
286
        this._infoMessageLabel.set_text(text);
 
287
        this._infoMessageLabel.show();
 
288
        this._errorMessageLabel.hide();
 
289
        this._nullMessageLabel.hide();
 
290
        this._ensureOpen();
 
291
    },
 
292
 
 
293
    destroySession: function() {
 
294
        if (this._session) {
 
295
            if (!this._completed)
 
296
                this._session.cancel();
 
297
            this._session = null;
 
298
        }
 
299
    },
 
300
 
 
301
    _onUserChanged: function() {
 
302
        if (this._user.is_loaded && this._userAvatar) {
 
303
            this._userAvatar.update();
 
304
            this._userAvatar.actor.show();
 
305
        }
 
306
    },
 
307
 
 
308
    cancel: function() {
 
309
        this._wasDismissed = true;
 
310
        this.close(global.get_current_time());
 
311
        this._emitDone(false, true);
 
312
    },
 
313
});
 
314
Signals.addSignalMethods(AuthenticationDialog.prototype);
 
315
 
 
316
const AuthenticationAgent = new Lang.Class({
 
317
    Name: 'AuthenticationAgent',
 
318
 
 
319
    _init: function() {
 
320
        this._currentDialog = null;
 
321
        this._isCompleting = false;
 
322
        this._handle = null;
 
323
        this._native = new Shell.PolkitAuthenticationAgent();
 
324
        this._native.connect('initiate', Lang.bind(this, this._onInitiate));
 
325
        this._native.connect('cancel', Lang.bind(this, this._onCancel));
 
326
    },
 
327
 
 
328
    enable: function() {
 
329
        this._native.register();
 
330
    },
 
331
 
 
332
    disable: function() {
 
333
        this._native.unregister();
 
334
    },
 
335
 
 
336
    _onInitiate: function(nativeAgent, actionId, message, iconName, cookie, userNames) {
 
337
        this._currentDialog = new AuthenticationDialog(actionId, message, cookie, userNames);
 
338
 
 
339
        // We actually don't want to open the dialog until we know for
 
340
        // sure that we're going to interact with the user. For
 
341
        // example, if the password for the identity to auth is blank
 
342
        // (which it will be on a live CD) then there will be no
 
343
        // conversation at all... of course, we don't *know* that
 
344
        // until we actually try it.
 
345
        //
 
346
        // See https://bugzilla.gnome.org/show_bug.cgi?id=643062 for more
 
347
        // discussion.
 
348
 
 
349
        this._currentDialog.connect('done', Lang.bind(this, this._onDialogDone));
 
350
        this._currentDialog.startAuthentication();
 
351
    },
 
352
 
 
353
    _onCancel: function(nativeAgent) {
 
354
        this._completeRequest(false, false);
 
355
    },
 
356
 
 
357
    _onDialogDone: function(dialog, keepVisible, dismissed) {
 
358
        this._completeRequest(keepVisible, dismissed);
 
359
    },
 
360
 
 
361
    _reallyCompleteRequest: function(dismissed) {
 
362
        this._currentDialog.close();
 
363
        this._currentDialog.destroySession();
 
364
        this._currentDialog = null;
 
365
        this._isCompleting = false;
 
366
 
 
367
        this._native.complete(dismissed)
 
368
    },
 
369
 
 
370
    _completeRequest: function(keepVisible, wasDismissed) {
 
371
        if (this._isCompleting)
 
372
            return;
 
373
 
 
374
        this._isCompleting = true;
 
375
 
 
376
        if (keepVisible) {
 
377
            // Give the user 2 seconds to read 'Authentication Failure' before
 
378
            // dismissing the dialog
 
379
            Mainloop.timeout_add(2000,
 
380
                                 Lang.bind(this,
 
381
                                           function() {
 
382
                                               this._reallyCompleteRequest(wasDismissed);
 
383
                                               return false;
 
384
                                           }));
 
385
        } else {
 
386
            this._reallyCompleteRequest(wasDismissed);
 
387
        }
 
388
    }
 
389
});
 
390
 
 
391
const Component = AuthenticationAgent;