1
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
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;
16
const Components = imports.ui.components;
17
const ModalDialog = imports.ui.modalDialog;
18
const ShellEntry = imports.ui.shellEntry;
19
const UserMenu = imports.ui.userMenu;
21
const DIALOG_ICON_SIZE = 48;
23
const AuthenticationDialog = new Lang.Class({
24
Name: 'AuthenticationDialog',
25
Extends: ModalDialog.ModalDialog,
27
_init: function(actionId, message, cookie, userNames) {
28
this.parent({ styleClass: 'prompt-dialog' });
30
this.actionId = actionId;
31
this.message = message;
32
this.userNames = userNames;
33
this._wasDismissed = false;
34
this._completed = false;
36
let mainContentBox = new St.BoxLayout({ style_class: 'prompt-dialog-main-layout',
38
this.contentLayout.add(mainContentBox,
42
let icon = new St.Icon({ icon_name: 'dialog-password-symbolic' });
43
mainContentBox.add(icon,
46
x_align: St.Align.END,
47
y_align: St.Align.START });
49
let messageBox = new St.BoxLayout({ style_class: 'prompt-dialog-message-layout',
51
mainContentBox.add(messageBox,
52
{ y_align: St.Align.START });
54
this._subjectLabel = new St.Label({ style_class: 'prompt-dialog-headline',
55
text: _("Authentication Required") });
57
messageBox.add(this._subjectLabel,
59
y_align: St.Align.START });
61
this._descriptionLabel = new St.Label({ style_class: 'prompt-dialog-description',
63
this._descriptionLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
64
this._descriptionLabel.clutter_text.line_wrap = true;
66
messageBox.add(this._descriptionLabel,
68
y_align: St.Align.START });
70
if (userNames.length > 1) {
71
log('polkitAuthenticationAgent: Received ' + userNames.length +
72
' identities that can be used for authentication. Only ' +
76
let userName = GLib.get_user_name();
77
if (userNames.indexOf(userName) < 0)
79
if (userNames.indexOf(userName) < 0)
80
userName = userNames[0];
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));
89
// Special case 'root'
90
let userIsRoot = false;
91
if (userName == 'root') {
93
userRealName = _("Administrator");
97
let userLabel = new St.Label(({ style_class: 'polkit-dialog-user-root-label',
98
text: userRealName }));
99
messageBox.add(userLabel);
101
let userBox = new St.BoxLayout({ style_class: 'polkit-dialog-user-layout',
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,
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,
118
x_align: St.Align.END,
119
y_align: St.Align.MIDDLE });
122
this._onUserChanged();
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',
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,
135
this.setInitialKeyFocus(this._passwordEntry);
136
this._passwordBox.hide();
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();
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();
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
154
this._nullMessageLabel = new St.Label({ style_class: 'prompt-dialog-null-label',
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();
162
this.setButtons([{ label: _("Cancel"),
163
action: Lang.bind(this, this.cancel),
166
{ label: _("Authenticate"),
167
action: Lang.bind(this, this._onAuthenticateButtonPressed),
171
this._doneEmitted = false;
173
this._identityToAuth = Polkit.UnixUser.new_for_name(userName);
174
this._cookie = cookie;
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));
184
startAuthentication: function() {
185
this._session.initiate();
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
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.
198
// One way to make this happen is by running 'sleep 3;
199
// pkexec bash' and then opening a popup menu.
201
// We could add retrying if this turns out to be a problem
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);
210
_emitDone: function(keepVisible, dismissed) {
211
if (!this._doneEmitted) {
212
this._doneEmitted = true;
213
this.emit('done', keepVisible, dismissed);
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();
227
_onAuthenticateButtonPressed: function() {
228
this._onEntryActivate();
231
_onSessionCompleted: function(session, gainedAuthorization) {
235
this._completed = true;
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."
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),
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();
254
this._emitDone(!gainedAuthorization, false);
257
_onSessionRequest: function(session, request, echo_on) {
258
// Cheap localization trick
259
if (request == 'Password:' || request == 'Password: ')
260
this._passwordLabel.set_text(_("Password:"));
262
this._passwordLabel.set_text(request);
265
this._passwordEntry.clutter_text.set_password_char('');
267
this._passwordEntry.clutter_text.set_password_char('\u25cf'); // ● U+25CF BLACK CIRCLE
269
this._passwordBox.show();
270
this._passwordEntry.set_text('');
271
this._passwordEntry.grab_key_focus();
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();
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();
293
destroySession: function() {
295
if (!this._completed)
296
this._session.cancel();
297
this._session = null;
301
_onUserChanged: function() {
302
if (this._user.is_loaded && this._userAvatar) {
303
this._userAvatar.update();
304
this._userAvatar.actor.show();
309
this._wasDismissed = true;
310
this.close(global.get_current_time());
311
this._emitDone(false, true);
314
Signals.addSignalMethods(AuthenticationDialog.prototype);
316
const AuthenticationAgent = new Lang.Class({
317
Name: 'AuthenticationAgent',
320
this._currentDialog = null;
321
this._isCompleting = false;
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));
329
this._native.register();
332
disable: function() {
333
this._native.unregister();
336
_onInitiate: function(nativeAgent, actionId, message, iconName, cookie, userNames) {
337
this._currentDialog = new AuthenticationDialog(actionId, message, cookie, userNames);
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.
346
// See https://bugzilla.gnome.org/show_bug.cgi?id=643062 for more
349
this._currentDialog.connect('done', Lang.bind(this, this._onDialogDone));
350
this._currentDialog.startAuthentication();
353
_onCancel: function(nativeAgent) {
354
this._completeRequest(false, false);
357
_onDialogDone: function(dialog, keepVisible, dismissed) {
358
this._completeRequest(keepVisible, dismissed);
361
_reallyCompleteRequest: function(dismissed) {
362
this._currentDialog.close();
363
this._currentDialog.destroySession();
364
this._currentDialog = null;
365
this._isCompleting = false;
367
this._native.complete(dismissed)
370
_completeRequest: function(keepVisible, wasDismissed) {
371
if (this._isCompleting)
374
this._isCompleting = true;
377
// Give the user 2 seconds to read 'Authentication Failure' before
378
// dismissing the dialog
379
Mainloop.timeout_add(2000,
382
this._reallyCompleteRequest(wasDismissed);
386
this._reallyCompleteRequest(wasDismissed);
391
const Component = AuthenticationAgent;