Blame js/ui/components/polkitAgent.js

Packit d345d1
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
Packit d345d1
Packit d345d1
const { AccountsService, Clutter, Gio, GLib,
Packit d345d1
        Pango, PolkitAgent, Polkit, Shell, St } = imports.gi;
Packit d345d1
const Signals = imports.signals;
Packit d345d1
Packit d345d1
const Animation = imports.ui.animation;
Packit d345d1
const Dialog = imports.ui.dialog;
Packit d345d1
const Main = imports.ui.main;
Packit d345d1
const ModalDialog = imports.ui.modalDialog;
Packit d345d1
const ShellEntry = imports.ui.shellEntry;
Packit d345d1
const UserWidget = imports.ui.userWidget;
Packit d345d1
Packit d345d1
var DIALOG_ICON_SIZE = 48;
Packit d345d1
Packit d345d1
var WORK_SPINNER_ICON_SIZE = 16;
Packit d345d1
Packit d345d1
var AuthenticationDialog = class extends ModalDialog.ModalDialog {
Packit d345d1
    constructor(actionId, body, cookie, userNames) {
Packit d345d1
        super({ styleClass: 'prompt-dialog' });
Packit d345d1
Packit d345d1
        this.actionId = actionId;
Packit d345d1
        this.message = body;
Packit d345d1
        this.userNames = userNames;
Packit d345d1
        this._wasDismissed = false;
Packit d345d1
Packit d345d1
        this._sessionUpdatedId = Main.sessionMode.connect('updated', () => {
Packit d345d1
            this._group.visible = !Main.sessionMode.isLocked;
Packit d345d1
        });
Packit d345d1
Packit d345d1
        this.connect('closed', this._onDialogClosed.bind(this));
Packit d345d1
Packit d345d1
        let icon = new Gio.ThemedIcon({ name: 'dialog-password-symbolic' });
Packit d345d1
        let title = _("Authentication Required");
Packit d345d1
Packit d345d1
        let content = new Dialog.MessageDialogContent({ icon, title, body });
Packit d345d1
        this.contentLayout.add_actor(content);
Packit d345d1
Packit d345d1
        if (userNames.length > 1) {
Packit d345d1
            log('polkitAuthenticationAgent: Received ' + userNames.length +
Packit d345d1
                ' identities that can be used for authentication. Only ' +
Packit d345d1
                'considering one.');
Packit d345d1
        }
Packit d345d1
Packit d345d1
        let userName = GLib.get_user_name();
Packit d345d1
        if (userNames.indexOf(userName) < 0)
Packit d345d1
            userName = 'root';
Packit d345d1
        if (userNames.indexOf(userName) < 0)
Packit d345d1
            userName = userNames[0];
Packit d345d1
Packit d345d1
        this._user = AccountsService.UserManager.get_default().get_user(userName);
Packit d345d1
        let userRealName = this._user.get_real_name()
Packit d345d1
        this._userLoadedId = this._user.connect('notify::is_loaded',
Packit d345d1
                                                this._onUserChanged.bind(this));
Packit d345d1
        this._userChangedId = this._user.connect('changed',
Packit d345d1
                                                 this._onUserChanged.bind(this));
Packit d345d1
Packit d345d1
        // Special case 'root'
Packit d345d1
        let userIsRoot = false;
Packit d345d1
        if (userName == 'root') {
Packit d345d1
            userIsRoot = true;
Packit d345d1
            userRealName = _("Administrator");
Packit d345d1
        }
Packit d345d1
Packit d345d1
        if (userIsRoot) {
Packit d345d1
            let userLabel = new St.Label(({ style_class: 'polkit-dialog-user-root-label',
Packit d345d1
                                            text: userRealName }));
Packit d345d1
            content.messageBox.add(userLabel, { x_fill: false,
Packit d345d1
                                                x_align: St.Align.START });
Packit d345d1
        } else {
Packit d345d1
            let userBox = new St.BoxLayout({ style_class: 'polkit-dialog-user-layout',
Packit d345d1
                                             vertical: false });
Packit d345d1
            content.messageBox.add(userBox);
Packit d345d1
            this._userAvatar = new UserWidget.Avatar(this._user,
Packit d345d1
                                                     { iconSize: DIALOG_ICON_SIZE,
Packit d345d1
                                                       styleClass: 'polkit-dialog-user-icon' });
Packit d345d1
            this._userAvatar.actor.hide();
Packit d345d1
            userBox.add(this._userAvatar.actor,
Packit d345d1
                        { x_fill:  true,
Packit d345d1
                          y_fill:  false,
Packit d345d1
                          x_align: St.Align.END,
Packit d345d1
                          y_align: St.Align.START });
Packit d345d1
            let userLabel = new St.Label(({ style_class: 'polkit-dialog-user-label',
Packit d345d1
                                            text: userRealName }));
Packit d345d1
            userBox.add(userLabel,
Packit d345d1
                        { x_fill:  true,
Packit d345d1
                          y_fill:  false,
Packit d345d1
                          x_align: St.Align.END,
Packit d345d1
                          y_align: St.Align.MIDDLE });
Packit d345d1
        }
Packit d345d1
Packit d345d1
        this._onUserChanged();
Packit d345d1
Packit d345d1
        this._passwordBox = new St.BoxLayout({ vertical: false, style_class: 'prompt-dialog-password-box' });
Packit d345d1
        content.messageBox.add(this._passwordBox);
Packit d345d1
        this._passwordLabel = new St.Label(({ style_class: 'prompt-dialog-password-label' }));
Packit d345d1
        this._passwordBox.add(this._passwordLabel, { y_fill: false, y_align: St.Align.MIDDLE });
Packit d345d1
        this._passwordEntry = new St.Entry({ style_class: 'prompt-dialog-password-entry',
Packit d345d1
                                             text: "",
Packit d345d1
                                             can_focus: true});
Packit d345d1
        ShellEntry.addContextMenu(this._passwordEntry, { isPassword: true });
Packit d345d1
        this._passwordEntry.clutter_text.connect('activate', this._onEntryActivate.bind(this));
Packit d345d1
        this._passwordBox.add(this._passwordEntry,
Packit d345d1
                              { expand: true });
Packit d345d1
Packit d345d1
        this._workSpinner = new Animation.Spinner(WORK_SPINNER_ICON_SIZE, true);
Packit d345d1
        this._passwordBox.add(this._workSpinner.actor);
Packit d345d1
Packit d345d1
        this.setInitialKeyFocus(this._passwordEntry);
Packit d345d1
        this._passwordBox.hide();
Packit d345d1
Packit d345d1
        this._errorMessageLabel = new St.Label({ style_class: 'prompt-dialog-error-label' });
Packit d345d1
        this._errorMessageLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
Packit d345d1
        this._errorMessageLabel.clutter_text.line_wrap = true;
Packit d345d1
        content.messageBox.add(this._errorMessageLabel, { x_fill: false, x_align: St.Align.START });
Packit d345d1
        this._errorMessageLabel.hide();
Packit d345d1
Packit d345d1
        this._infoMessageLabel = new St.Label({ style_class: 'prompt-dialog-info-label' });
Packit d345d1
        this._infoMessageLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
Packit d345d1
        this._infoMessageLabel.clutter_text.line_wrap = true;
Packit d345d1
        content.messageBox.add(this._infoMessageLabel);
Packit d345d1
        this._infoMessageLabel.hide();
Packit d345d1
Packit d345d1
        /* text is intentionally non-blank otherwise the height is not the same as for
Packit d345d1
         * infoMessage and errorMessageLabel - but it is still invisible because
Packit d345d1
         * gnome-shell.css sets the color to be transparent
Packit d345d1
         */
Packit d345d1
        this._nullMessageLabel = new St.Label({ style_class: 'prompt-dialog-null-label',
Packit d345d1
                                                text: 'abc'});
Packit d345d1
        this._nullMessageLabel.add_style_class_name('hidden');
Packit d345d1
        this._nullMessageLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
Packit d345d1
        this._nullMessageLabel.clutter_text.line_wrap = true;
Packit d345d1
        content.messageBox.add(this._nullMessageLabel);
Packit d345d1
        this._nullMessageLabel.show();
Packit d345d1
Packit d345d1
        this._cancelButton = this.addButton({ label: _("Cancel"),
Packit d345d1
                                              action: this.cancel.bind(this),
Packit d345d1
                                              key: Clutter.Escape });
Packit d345d1
        this._okButton = this.addButton({ label:  _("Authenticate"),
Packit d345d1
                                          action: this._onAuthenticateButtonPressed.bind(this),
Packit d345d1
                                          default: true });
Packit d345d1
Packit d345d1
        this._doneEmitted = false;
Packit d345d1
Packit d345d1
        this._identityToAuth = Polkit.UnixUser.new_for_name(userName);
Packit d345d1
        this._cookie = cookie;
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _setWorking(working) {
Packit d345d1
        if (working)
Packit d345d1
            this._workSpinner.play();
Packit d345d1
        else
Packit d345d1
            this._workSpinner.stop();
Packit d345d1
    }
Packit d345d1
Packit d345d1
    performAuthentication() {
Packit d345d1
        this._destroySession();
Packit d345d1
        this._session = new PolkitAgent.Session({ identity: this._identityToAuth,
Packit d345d1
                                                  cookie: this._cookie });
Packit d345d1
        this._sessionCompletedId = this._session.connect('completed', this._onSessionCompleted.bind(this));
Packit d345d1
        this._sessionRequestId = this._session.connect('request', this._onSessionRequest.bind(this));
Packit d345d1
        this._sessionShowErrorId = this._session.connect('show-error', this._onSessionShowError.bind(this));
Packit d345d1
        this._sessionShowInfoId = this._session.connect('show-info', this._onSessionShowInfo.bind(this));
Packit d345d1
        this._session.initiate();
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _ensureOpen() {
Packit d345d1
        // NOTE: ModalDialog.open() is safe to call if the dialog is
Packit d345d1
        // already open - it just returns true without side-effects
Packit d345d1
        if (!this.open(global.get_current_time())) {
Packit d345d1
            // This can fail if e.g. unable to get input grab
Packit d345d1
            //
Packit d345d1
            // In an ideal world this wouldn't happen (because the
Packit d345d1
            // Shell is in complete control of the session) but that's
Packit d345d1
            // just not how things work right now.
Packit d345d1
            //
Packit d345d1
            // One way to make this happen is by running 'sleep 3;
Packit d345d1
            // pkexec bash' and then opening a popup menu.
Packit d345d1
            //
Packit d345d1
            // We could add retrying if this turns out to be a problem
Packit d345d1
Packit d345d1
            log('polkitAuthenticationAgent: Failed to show modal dialog.' +
Packit d345d1
                ' Dismissing authentication request for action-id ' + this.actionId +
Packit d345d1
                ' cookie ' + this._cookie);
Packit d345d1
            this._emitDone(true);
Packit d345d1
        }
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _emitDone(dismissed) {
Packit d345d1
        if (!this._doneEmitted) {
Packit d345d1
            this._doneEmitted = true;
Packit d345d1
            this.emit('done', dismissed);
Packit d345d1
        }
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _updateSensitivity(sensitive) {
Packit d345d1
        this._passwordEntry.reactive = sensitive;
Packit d345d1
        this._passwordEntry.clutter_text.editable = sensitive;
Packit d345d1
Packit d345d1
        this._okButton.can_focus = sensitive;
Packit d345d1
        this._okButton.reactive = sensitive;
Packit d345d1
        this._setWorking(!sensitive);
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _onEntryActivate() {
Packit d345d1
        let response = this._passwordEntry.get_text();
Packit d345d1
        this._updateSensitivity(false);
Packit d345d1
        this._session.response(response);
Packit d345d1
        // When the user responds, dismiss already shown info and
Packit d345d1
        // error texts (if any)
Packit d345d1
        this._errorMessageLabel.hide();
Packit d345d1
        this._infoMessageLabel.hide();
Packit d345d1
        this._nullMessageLabel.show();
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _onAuthenticateButtonPressed() {
Packit d345d1
        this._onEntryActivate();
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _onSessionCompleted(session, gainedAuthorization) {
Packit d345d1
        if (this._completed || this._doneEmitted)
Packit d345d1
            return;
Packit d345d1
Packit d345d1
        this._completed = true;
Packit d345d1
Packit d345d1
        /* Yay, all done */
Packit d345d1
        if (gainedAuthorization) {
Packit d345d1
            this._emitDone(false);
Packit d345d1
Packit d345d1
        } else {
Packit d345d1
            /* Unless we are showing an existing error message from the PAM
Packit d345d1
             * module (the PAM module could be reporting the authentication
Packit d345d1
             * error providing authentication-method specific information),
Packit d345d1
             * show "Sorry, that didn't work. Please try again."
Packit d345d1
             */
Packit d345d1
            if (!this._errorMessageLabel.visible && !this._wasDismissed) {
Packit d345d1
                /* Translators: "that didn't work" refers to the fact that the
Packit d345d1
                 * requested authentication was not gained; this can happen
Packit d345d1
                 * because of an authentication error (like invalid password),
Packit d345d1
                 * for instance. */
Packit d345d1
                this._errorMessageLabel.set_text(_("Sorry, that didn’t work. Please try again."));
Packit d345d1
                this._errorMessageLabel.show();
Packit d345d1
                this._infoMessageLabel.hide();
Packit d345d1
                this._nullMessageLabel.hide();
Packit d345d1
            }
Packit d345d1
Packit d345d1
            /* Try and authenticate again */
Packit d345d1
            this.performAuthentication();
Packit d345d1
        }
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _onSessionRequest(session, request, echo_on) {
Packit d345d1
        // Cheap localization trick
Packit d345d1
        if (request == 'Password:' || request == 'Password: ')
Packit d345d1
            this._passwordLabel.set_text(_("Password:"));
Packit d345d1
        else
Packit d345d1
            this._passwordLabel.set_text(request);
Packit d345d1
Packit d345d1
        if (echo_on)
Packit d345d1
            this._passwordEntry.clutter_text.set_password_char('');
Packit d345d1
        else
Packit d345d1
            this._passwordEntry.clutter_text.set_password_char('\u25cf'); // ● U+25CF BLACK CIRCLE
Packit d345d1
Packit d345d1
        this._passwordBox.show();
Packit d345d1
        this._passwordEntry.set_text('');
Packit d345d1
        this._passwordEntry.grab_key_focus();
Packit d345d1
        this._updateSensitivity(true);
Packit d345d1
        this._ensureOpen();
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _onSessionShowError(session, text) {
Packit d345d1
        this._passwordEntry.set_text('');
Packit d345d1
        this._errorMessageLabel.set_text(text);
Packit d345d1
        this._errorMessageLabel.show();
Packit d345d1
        this._infoMessageLabel.hide();
Packit d345d1
        this._nullMessageLabel.hide();
Packit d345d1
        this._ensureOpen();
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _onSessionShowInfo(session, text) {
Packit d345d1
        this._passwordEntry.set_text('');
Packit d345d1
        this._infoMessageLabel.set_text(text);
Packit d345d1
        this._infoMessageLabel.show();
Packit d345d1
        this._errorMessageLabel.hide();
Packit d345d1
        this._nullMessageLabel.hide();
Packit d345d1
        this._ensureOpen();
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _destroySession() {
Packit d345d1
        if (this._session) {
Packit d345d1
            if (!this._completed)
Packit d345d1
                this._session.cancel();
Packit d345d1
            this._completed = false;
Packit d345d1
Packit d345d1
            this._session.disconnect(this._sessionCompletedId);
Packit d345d1
            this._session.disconnect(this._sessionRequestId);
Packit d345d1
            this._session.disconnect(this._sessionShowErrorId);
Packit d345d1
            this._session.disconnect(this._sessionShowInfoId);
Packit d345d1
            this._session = null;
Packit d345d1
        }
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _onUserChanged() {
Packit d345d1
        if (this._user.is_loaded && this._userAvatar) {
Packit d345d1
            this._userAvatar.update();
Packit d345d1
            this._userAvatar.actor.show();
Packit d345d1
        }
Packit d345d1
    }
Packit d345d1
Packit d345d1
    cancel() {
Packit d345d1
        this._wasDismissed = true;
Packit d345d1
        this.close(global.get_current_time());
Packit d345d1
        this._emitDone(true);
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _onDialogClosed() {
Packit d345d1
        if (this._sessionUpdatedId)
Packit d345d1
            Main.sessionMode.disconnect(this._sessionUpdatedId);
Packit d345d1
        this._sessionUpdatedId = 0;
Packit d345d1
Packit d345d1
        if (this._user) {
Packit d345d1
            this._user.disconnect(this._userLoadedId);
Packit d345d1
            this._user.disconnect(this._userChangedId);
Packit d345d1
            this._user = null;
Packit d345d1
        }
Packit d345d1
Packit d345d1
        this._destroySession();
Packit d345d1
    }
Packit d345d1
};
Packit d345d1
Signals.addSignalMethods(AuthenticationDialog.prototype);
Packit d345d1
Packit d345d1
var AuthenticationAgent = class {
Packit d345d1
    constructor() {
Packit d345d1
        this._currentDialog = null;
Packit d345d1
        this._handle = null;
Packit d345d1
        this._native = new Shell.PolkitAuthenticationAgent();
Packit d345d1
        this._native.connect('initiate', this._onInitiate.bind(this));
Packit d345d1
        this._native.connect('cancel', this._onCancel.bind(this));
Packit d345d1
        this._sessionUpdatedId = 0;
Packit d345d1
    }
Packit d345d1
Packit d345d1
    enable() {
Packit d345d1
        try {
Packit d345d1
            this._native.register();
Packit d345d1
        } catch(e) {
Packit d345d1
            log('Failed to register AuthenticationAgent');
Packit d345d1
        }
Packit d345d1
    }
Packit d345d1
Packit d345d1
    disable() {
Packit d345d1
        try {
Packit d345d1
            this._native.unregister();
Packit d345d1
        } catch(e) {
Packit d345d1
            log('Failed to unregister AuthenticationAgent');
Packit d345d1
        }
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _onInitiate(nativeAgent, actionId, message, iconName, cookie, userNames) {
Packit d345d1
        // Don't pop up a dialog while locked
Packit d345d1
        if (Main.sessionMode.isLocked) {
Packit d345d1
            this._sessionUpdatedId = Main.sessionMode.connect('updated', () => {
Packit d345d1
                Main.sessionMode.disconnect(this._sessionUpdatedId);
Packit d345d1
                this._sessionUpdatedId = 0;
Packit d345d1
Packit d345d1
                this._onInitiate(nativeAgent, actionId, message, iconName, cookie, userNames);
Packit d345d1
            });
Packit d345d1
            return;
Packit d345d1
        }
Packit d345d1
Packit d345d1
        this._currentDialog = new AuthenticationDialog(actionId, message, cookie, userNames);
Packit d345d1
Packit d345d1
        // We actually don't want to open the dialog until we know for
Packit d345d1
        // sure that we're going to interact with the user. For
Packit d345d1
        // example, if the password for the identity to auth is blank
Packit d345d1
        // (which it will be on a live CD) then there will be no
Packit d345d1
        // conversation at all... of course, we don't *know* that
Packit d345d1
        // until we actually try it.
Packit d345d1
        //
Packit d345d1
        // See https://bugzilla.gnome.org/show_bug.cgi?id=643062 for more
Packit d345d1
        // discussion.
Packit d345d1
Packit d345d1
        this._currentDialog.connect('done', this._onDialogDone.bind(this));
Packit d345d1
        this._currentDialog.performAuthentication();
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _onCancel(nativeAgent) {
Packit d345d1
        this._completeRequest(false);
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _onDialogDone(dialog, dismissed) {
Packit d345d1
        this._completeRequest(dismissed);
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _completeRequest(dismissed) {
Packit d345d1
        this._currentDialog.close();
Packit d345d1
        this._currentDialog = null;
Packit d345d1
Packit d345d1
        if (this._sessionUpdatedId)
Packit d345d1
            Main.sessionMode.disconnect(this._sessionUpdatedId);
Packit d345d1
        this._sessionUpdatedId = 0;
Packit d345d1
Packit d345d1
        this._native.complete(dismissed);
Packit d345d1
    }
Packit d345d1
};
Packit d345d1
Packit d345d1
var Component = AuthenticationAgent;