Advertisement
Guest User

Untitled

a guest
Jul 25th, 2020
312
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
  2. /* exported Component */
  3.  
  4. const { AccountsService, Clutter, GLib,
  5.         GObject, Pango, PolkitAgent, Polkit, Shell, St } = imports.gi;
  6.  
  7. const Dialog = imports.ui.dialog;
  8. const Main = imports.ui.main;
  9. const ModalDialog = imports.ui.modalDialog;
  10. const ShellEntry = imports.ui.shellEntry;
  11. const UserWidget = imports.ui.userWidget;
  12. const Util = imports.misc.util;
  13.  
  14. const DialogMode = {
  15.     AUTH: 0,
  16.     CONFIRM: 1,
  17. };
  18.  
  19. const DIALOG_ICON_SIZE = 64;
  20.  
  21. const DELAYED_RESET_TIMEOUT = 200;
  22.  
  23. var AuthenticationDialog = GObject.registerClass({
  24.     Signals: { 'done': { param_types: [GObject.TYPE_BOOLEAN] } },
  25. }, class AuthenticationDialog extends ModalDialog.ModalDialog {
  26.     _init(actionId, description, cookie, userNames) {
  27.         super._init({ styleClass: 'prompt-dialog' });
  28.  
  29.         this.actionId = actionId;
  30.         this.message = description;
  31.         this.userNames = userNames;
  32.  
  33.         this._sessionUpdatedId = Main.sessionMode.connect('updated', () => {
  34.             this.visible = !Main.sessionMode.isLocked;
  35.         });
  36.  
  37.         this.connect('closed', this._onDialogClosed.bind(this));
  38.  
  39.         let title = _("Authentication Required");
  40.  
  41.         let headerContent = new Dialog.MessageDialogContent({ title, description });
  42.         this.contentLayout.add_child(headerContent);
  43.  
  44.         let bodyContent = new Dialog.MessageDialogContent();
  45.  
  46.         if (userNames.length > 1) {
  47.             log('polkitAuthenticationAgent: Received %d'.format(userNames.length) +
  48.                 'identities that can be used for authentication. Only ' +
  49.                 'considering one.');
  50.         }
  51.  
  52.         let userName = GLib.get_user_name();
  53.         if (!userNames.includes(userName))
  54.             userName = 'root';
  55.         if (!userNames.includes(userName))
  56.             userName = userNames[0];
  57.  
  58.         this._user = AccountsService.UserManager.get_default().get_user(userName);
  59.  
  60.         let userBox = new St.BoxLayout({
  61.             style_class: 'polkit-dialog-user-layout',
  62.             vertical: true,
  63.         });
  64.         bodyContent.add_child(userBox);
  65.  
  66.         this._userAvatar = new UserWidget.Avatar(this._user, {
  67.             iconSize: DIALOG_ICON_SIZE,
  68.             styleClass: 'polkit-dialog-user-icon',
  69.         });
  70.         this._userAvatar.x_align = Clutter.ActorAlign.CENTER;
  71.         userBox.add_child(this._userAvatar);
  72.  
  73.         this._userLabel = new St.Label({
  74.             style_class: userName === 'root'
  75.                 ? 'polkit-dialog-user-root-label'
  76.                 : 'polkit-dialog-user-label',
  77.         });
  78.  
  79.         if (userName === 'root')
  80.             this._userLabel.text = _('Administrator');
  81.  
  82.         userBox.add_child(this._userLabel);
  83.  
  84.         let passwordBox = new St.BoxLayout({
  85.             style_class: 'prompt-dialog-password-layout',
  86.             vertical: true,
  87.         });
  88.  
  89.         this._passwordEntry = new St.PasswordEntry({
  90.             style_class: 'prompt-dialog-password-entry',
  91.             text: "",
  92.             can_focus: true,
  93.             visible: false,
  94.             x_align: Clutter.ActorAlign.CENTER,
  95.         });
  96.         ShellEntry.addContextMenu(this._passwordEntry);
  97.         this._passwordEntry.clutter_text.connect('activate', this._onEntryActivate.bind(this));
  98.         this._passwordEntry.bind_property('reactive',
  99.             this._passwordEntry.clutter_text, 'editable',
  100.             GObject.BindingFlags.SYNC_CREATE);
  101.         passwordBox.add_child(this._passwordEntry);
  102.  
  103.         let warningBox = new St.BoxLayout({ vertical: true });
  104.  
  105.         let capsLockWarning = new ShellEntry.CapsLockWarning();
  106.         this._passwordEntry.bind_property('visible',
  107.             capsLockWarning, 'visible',
  108.             GObject.BindingFlags.SYNC_CREATE);
  109.         warningBox.add_child(capsLockWarning);
  110.  
  111.         this._errorMessageLabel = new St.Label({
  112.             style_class: 'prompt-dialog-error-label',
  113.             visible: false,
  114.         });
  115.         this._errorMessageLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
  116.         this._errorMessageLabel.clutter_text.line_wrap = true;
  117.         warningBox.add_child(this._errorMessageLabel);
  118.  
  119.         this._infoMessageLabel = new St.Label({
  120.             style_class: 'prompt-dialog-info-label',
  121.             visible: false,
  122.         });
  123.         this._infoMessageLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
  124.         this._infoMessageLabel.clutter_text.line_wrap = true;
  125.         warningBox.add_child(this._infoMessageLabel);
  126.  
  127.         /* text is intentionally non-blank otherwise the height is not the same as for
  128.          * infoMessage and errorMessageLabel - but it is still invisible because
  129.          * gnome-shell.css sets the color to be transparent
  130.          */
  131.         this._nullMessageLabel = new St.Label({ style_class: 'prompt-dialog-null-label' });
  132.         this._nullMessageLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
  133.         this._nullMessageLabel.clutter_text.line_wrap = true;
  134.         warningBox.add_child(this._nullMessageLabel);
  135.  
  136.         passwordBox.add_child(warningBox);
  137.         bodyContent.add_child(passwordBox);
  138.  
  139.         this._cancelButton = this.addButton({ label: _("Cancel"),
  140.                                               action: this.cancel.bind(this),
  141.                                               key: Clutter.KEY_Escape });
  142.         this._okButton = this.addButton({ label: _("Authenticate"),
  143.                                           action: this._onAuthenticateButtonPressed.bind(this),
  144.                                           reactive: false });
  145.         this._okButton.bind_property('reactive',
  146.             this._okButton, 'can-focus',
  147.             GObject.BindingFlags.SYNC_CREATE);
  148.  
  149.         this._passwordEntry.clutter_text.connect('text-changed', text => {
  150.             this._okButton.reactive = text.get_text().length > 0;
  151.         });
  152.  
  153.         this.contentLayout.add_child(bodyContent);
  154.  
  155.         this._doneEmitted = false;
  156.  
  157.         this._mode = -1;
  158.  
  159.         this._identityToAuth = Polkit.UnixUser.new_for_name(userName);
  160.         this._cookie = cookie;
  161.  
  162.         this._userLoadedId = this._user.connect('notify::is-loaded',
  163.             this._onUserChanged.bind(this));
  164.         this._userChangedId = this._user.connect('changed',
  165.             this._onUserChanged.bind(this));
  166.         this._onUserChanged();
  167.     }
  168.  
  169.     _initiateSession() {
  170.         this._destroySession(DELAYED_RESET_TIMEOUT);
  171.  
  172.         this._session = new PolkitAgent.Session({ identity: this._identityToAuth,
  173.                                                   cookie: this._cookie });
  174.         this._sessionCompletedId = this._session.connect('completed', this._onSessionCompleted.bind(this));
  175.         this._sessionRequestId = this._session.connect('request', this._onSessionRequest.bind(this));
  176.         this._sessionShowErrorId = this._session.connect('show-error', this._onSessionShowError.bind(this));
  177.         this._sessionShowInfoId = this._session.connect('show-info', this._onSessionShowInfo.bind(this));
  178.         this._session.initiate();
  179.     }
  180.  
  181.     _ensureOpen() {
  182.         // NOTE: ModalDialog.open() is safe to call if the dialog is
  183.         // already open - it just returns true without side-effects
  184.         if (!this.open(global.get_current_time())) {
  185.             // This can fail if e.g. unable to get input grab
  186.             //
  187.             // In an ideal world this wouldn't happen (because the
  188.             // Shell is in complete control of the session) but that's
  189.             // just not how things work right now.
  190.             //
  191.             // One way to make this happen is by running 'sleep 3;
  192.             // pkexec bash' and then opening a popup menu.
  193.             //
  194.             // We could add retrying if this turns out to be a problem
  195.  
  196.             log('polkitAuthenticationAgent: Failed to show modal dialog. ' +
  197.                 'Dismissing authentication request for action-id %s '.format(this.actionId) +
  198.                 'cookie %s'.format(this._cookie));
  199.             this._emitDone(true);
  200.         }
  201.     }
  202.  
  203.     _emitDone(dismissed) {
  204.         if (!this._doneEmitted) {
  205.             this._doneEmitted = true;
  206.             this.emit('done', dismissed);
  207.         }
  208.     }
  209.  
  210.     _onEntryActivate() {
  211.         let response = this._passwordEntry.get_text();
  212.         if (response.length === 0)
  213.             return;
  214.  
  215.         this._passwordEntry.reactive = false;
  216.         this._okButton.reactive = false;
  217.  
  218.         this._session.response(response);
  219.         // When the user responds, dismiss already shown info and
  220.         // error texts (if any)
  221.         this._errorMessageLabel.hide();
  222.         this._infoMessageLabel.hide();
  223.         this._nullMessageLabel.show();
  224.     }
  225.  
  226.     _onAuthenticateButtonPressed() {
  227.         if (this._mode === DialogMode.CONFIRM)
  228.             this._initiateSession();
  229.         else
  230.             this._onEntryActivate();
  231.     }
  232.  
  233.     _onSessionCompleted(session, gainedAuthorization) {
  234.         if (this._completed || this._doneEmitted)
  235.             return;
  236.  
  237.         this._completed = true;
  238.  
  239.         /* Yay, all done */
  240.         if (gainedAuthorization) {
  241.             this._emitDone(false);
  242.  
  243.         } else {
  244.             /* Unless we are showing an existing error message from the PAM
  245.              * module (the PAM module could be reporting the authentication
  246.              * error providing authentication-method specific information),
  247.              * show "Sorry, that didn't work. Please try again."
  248.              */
  249.             if (!this._errorMessageLabel.visible) {
  250.                 /* Translators: "that didn't work" refers to the fact that the
  251.                  * requested authentication was not gained; this can happen
  252.                  * because of an authentication error (like invalid password),
  253.                  * for instance. */
  254.                 this._errorMessageLabel.set_text(_("Sorry, that didn’t work. Please try again."));
  255.                 this._errorMessageLabel.show();
  256.                 this._infoMessageLabel.hide();
  257.                 this._nullMessageLabel.hide();
  258.  
  259.                 Util.wiggle(this._passwordEntry);
  260.             }
  261.  
  262.             /* Try and authenticate again */
  263.             this._initiateSession();
  264.         }
  265.     }
  266.  
  267.     _onSessionRequest(session, request, echoOn) {
  268.         if (this._sessionRequestTimeoutId) {
  269.             GLib.source_remove(this._sessionRequestTimeoutId);
  270.             this._sessionRequestTimeoutId = 0;
  271.         }
  272.  
  273.         // Hack: The request string comes directly from PAM, if it's "Password:"
  274.         // we replace it with our own to allow localization, if it's something
  275.         // else we remove the last colon and any trailing or leading spaces.
  276.         if (request === 'Password:' || request === 'Password: ')
  277.             this._passwordEntry.hint_text = _('Password');
  278.         else
  279.             this._passwordEntry.hint_text = request.replace(/: *$/, '').trim();
  280.  
  281.         this._passwordEntry.password_visible = echoOn;
  282.  
  283.         this._passwordEntry.show();
  284.         this._passwordEntry.set_text('');
  285.         this._passwordEntry.reactive  = true;
  286.         this._okButton.reactive = false;
  287.  
  288.         this._ensureOpen();
  289.         this._passwordEntry.grab_key_focus();
  290.     }
  291.  
  292.     _onSessionShowError(session, text) {
  293.         this._passwordEntry.set_text('');
  294.         this._errorMessageLabel.set_text(text);
  295.         this._errorMessageLabel.show();
  296.         this._infoMessageLabel.hide();
  297.         this._nullMessageLabel.hide();
  298.         this._ensureOpen();
  299.     }
  300.  
  301.     _onSessionShowInfo(session, text) {
  302.         this._passwordEntry.set_text('');
  303.         this._infoMessageLabel.set_text(text);
  304.         this._infoMessageLabel.show();
  305.         this._errorMessageLabel.hide();
  306.         this._nullMessageLabel.hide();
  307.         this._ensureOpen();
  308.     }
  309.  
  310.     _destroySession(delay = 0) {
  311.         if (this._session) {
  312.             this._session.disconnect(this._sessionCompletedId);
  313.             this._session.disconnect(this._sessionRequestId);
  314.             this._session.disconnect(this._sessionShowErrorId);
  315.             this._session.disconnect(this._sessionShowInfoId);
  316.  
  317.             if (!this._completed)
  318.                 this._session.cancel();
  319.  
  320.             this._completed = false;
  321.             this._session = null;
  322.         }
  323.  
  324.         if (this._sessionRequestTimeoutId) {
  325.             GLib.source_remove(this._sessionRequestTimeoutId);
  326.             this._sessionRequestTimeoutId = 0;
  327.         }
  328.  
  329.         let resetDialog = () => {
  330.             if (this.state != ModalDialog.State.OPENED)
  331.                 return;
  332.  
  333.             this._passwordEntry.hide();
  334.             this._cancelButton.grab_key_focus();
  335.             this._okButton.reactive = false;
  336.         };
  337.  
  338.         if (delay) {
  339.             this._sessionRequestTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, delay, resetDialog);
  340.             GLib.Source.set_name_by_id(this._sessionRequestTimeoutId, '[gnome-shell] this._sessionRequestTimeoutId');
  341.         } else {
  342.             resetDialog();
  343.         }
  344.     }
  345.  
  346.     _onUserChanged() {
  347.         if (!this._user.is_loaded)
  348.             return;
  349.  
  350.         let userName = this._user.get_user_name();
  351.         let realName = this._user.get_real_name();
  352.  
  353.         if (userName !== 'root')
  354.             this._userLabel.set_text(realName);
  355.  
  356.         this._userAvatar.update();
  357.  
  358.         if (this._user.get_password_mode() === AccountsService.UserPasswordMode.NONE) {
  359.             if (this._mode === DialogMode.CONFIRM)
  360.                 return;
  361.  
  362.             this._mode = DialogMode.CONFIRM;
  363.             this._destroySession();
  364.  
  365.             this._okButton.reactive = true;
  366.  
  367.             /* We normally open the dialog when we get a "request" signal, but
  368.              * since in this case initiating a session would perform the
  369.              * authentication, only open the dialog and initiate the session
  370.              * when the user confirmed. */
  371.             this._ensureOpen();
  372.         } else {
  373.             if (this._mode === DialogMode.AUTH)
  374.                 return;
  375.  
  376.             this._mode = DialogMode.AUTH;
  377.             this._initiateSession();
  378.         }
  379.     }
  380.  
  381.     cancel() {
  382.         this.close(global.get_current_time());
  383.         this._emitDone(true);
  384.     }
  385.  
  386.     _onDialogClosed() {
  387.         if (this._sessionUpdatedId)
  388.             Main.sessionMode.disconnect(this._sessionUpdatedId);
  389.  
  390.         if (this._sessionRequestTimeoutId)
  391.             GLib.source_remove(this._sessionRequestTimeoutId);
  392.         this._sessionRequestTimeoutId = 0;
  393.  
  394.         if (this._user) {
  395.             this._user.disconnect(this._userLoadedId);
  396.             this._user.disconnect(this._userChangedId);
  397.             this._user = null;
  398.         }
  399.  
  400.         this._destroySession();
  401.     }
  402. });
  403.  
  404. var AuthenticationAgent = GObject.registerClass(
  405. class AuthenticationAgent extends Shell.PolkitAuthenticationAgent {
  406.     _init() {
  407.         super._init();
  408.  
  409.         this._currentDialog = null;
  410.         this.connect('initiate', this._onInitiate.bind(this));
  411.         this.connect('cancel', this._onCancel.bind(this));
  412.         this._sessionUpdatedId = 0;
  413.     }
  414.  
  415.     enable() {
  416.         try {
  417.             this.register();
  418.         } catch (e) {
  419.             log('Failed to register AuthenticationAgent');
  420.         }
  421.     }
  422.  
  423.     disable() {
  424.         try {
  425.             this.unregister();
  426.         } catch (e) {
  427.             log('Failed to unregister AuthenticationAgent');
  428.         }
  429.     }
  430.  
  431.     _onInitiate(nativeAgent, actionId, message, iconName, cookie, userNames) {
  432.         // Don't pop up a dialog while locked
  433.         if (Main.sessionMode.isLocked) {
  434.             this._sessionUpdatedId = Main.sessionMode.connect('updated', () => {
  435.                 Main.sessionMode.disconnect(this._sessionUpdatedId);
  436.                 this._sessionUpdatedId = 0;
  437.  
  438.                 this._onInitiate(nativeAgent, actionId, message, iconName, cookie, userNames);
  439.             });
  440.             return;
  441.         }
  442.  
  443.         this._currentDialog = new AuthenticationDialog(actionId, message, cookie, userNames);
  444.         this._currentDialog.connect('done', this._onDialogDone.bind(this));
  445.     }
  446.  
  447.     _onCancel(_nativeAgent) {
  448.         this._completeRequest(false);
  449.     }
  450.  
  451.     _onDialogDone(_dialog, dismissed) {
  452.         this._completeRequest(dismissed);
  453.     }
  454.  
  455.     _completeRequest(dismissed) {
  456.         this._currentDialog.close();
  457.         this._currentDialog = null;
  458.  
  459.         if (this._sessionUpdatedId)
  460.             Main.sessionMode.disconnect(this._sessionUpdatedId);
  461.         this._sessionUpdatedId = 0;
  462.  
  463.         this.complete(dismissed);
  464.     }
  465. });
  466.  
  467. var Component = AuthenticationAgent;
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement