Skip to content

Commit

Permalink
polkitDialog: Support multi-user selection
Browse files Browse the repository at this point in the history
Only show user choices if there is more than one available admin user and
the current user is not an admin. With this support added, enable the
dialog by default.
  • Loading branch information
JosephMcc committed Dec 2, 2024
1 parent e9ab164 commit 56808eb
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 57 deletions.
16 changes: 13 additions & 3 deletions data/theme/cinnamon-sass/widgets/_dialogs.scss
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,21 @@

&-user-layout {
text-align: center;
spacing: $base_margin;
margin-bottom: $base_padding;
spacing: $base_margin * 2;
}

&-user-root-label { color: $error_color; }
&-user-combo {
@extend %flat_button;
@extend %heading;

border-radius: $base_border_radius;
padding: $base_padding * 1.5 $base_padding * 6;

// special case the :insensitive button sinc we want
// the label to be the normal color when there are
// not multiple users
&:insensitive { color: $fg_color; }
}
}

// Audio selection dialog
Expand Down
4 changes: 1 addition & 3 deletions js/ui/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -443,9 +443,7 @@ function start() {
_initUserSession();
screenRecorder = new ScreenRecorder.ScreenRecorder();

if (Meta.is_wayland_compositor()) {
PolkitAuthenticationAgent.init();
}
PolkitAuthenticationAgent.init();

KeyringPrompt.init();

Expand Down
186 changes: 135 additions & 51 deletions js/ui/polkitAuthenticationAgent.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,24 +34,81 @@ const Polkit = imports.gi.Polkit;
const PolkitAgent = imports.gi.PolkitAgent;

const Dialog = imports.ui.dialog;
const Main = imports.ui.main;
const ModalDialog = imports.ui.modalDialog;
const CinnamonEntry = imports.ui.cinnamonEntry;
const PopupMenu = imports.ui.popupMenu;
const UserWidget = imports.ui.userWidget;
const Util = imports.misc.util;

const DIALOG_ICON_SIZE = 64;
const DIALOG_ICON_SIZE = 96;
const DELAYED_RESET_TIMEOUT = 200;

var AdminUser = class {
constructor(user) {
this._user = user;
this._userName = null;
this._realName = null;
this._avatar = null;

this._avatar = new UserWidget.Avatar(this._user, {
iconSize: DIALOG_ICON_SIZE,
});
this._avatar.x_align = Clutter.ActorAlign.CENTER;
this._avatar.visible = false;

this._userLoadedId = this._user.connect('notify::is-loaded',
this._onUserChanged.bind(this));
this._userChangedId = this._user.connect('changed',
this._onUserChanged.bind(this));
this._onUserChanged();
}

get avatar() {
return this._avatar;
}

get realName() {
return this._realName;
}

get userName() {
return this._userName;
}

_onUserChanged() {
if (!this._user.is_loaded)
return;

this._userName = this._user.get_user_name();
this._realName = this._user.get_real_name();

this._avatar.update();
}

destroy() {
if (this._user) {
this._user.disconnect(this._userLoadedId);
this._user.disconnect(this._userChangedId);
this._user = null;
}
}
};

var AuthenticationDialog = GObject.registerClass({
Signals: { 'done': { param_types: [GObject.TYPE_BOOLEAN] } }
}, class AuthenticationDialog extends ModalDialog.ModalDialog {
_init(actionId, description, cookie, userNames) {
super._init({ styleClass: 'prompt-dialog' });

this.actionId = actionId;
this._cookie = cookie;
this.message = description;
this.userNames = userNames;
this._wasDismissed = false;
this._user = null;
this._visibleAvatar = null;
this._adminUsers = [];

this.connect('closed', this._onDialogClosed.bind(this));

Expand All @@ -62,19 +119,8 @@ var AuthenticationDialog = GObject.registerClass({

let bodyContent = new Dialog.MessageDialogContent();

if (userNames.length > 1) {
log('polkitAuthenticationAgent: Received ' + userNames.length +
' identities that can be used for authentication. Only ' +
'considering the first one.');
}

let userName = GLib.get_user_name();
if (!userNames.includes(userName))
userName = 'root';
if (!userNames.includes(userName))
userName = userNames[0];

this._user = AccountsService.UserManager.get_default().get_user(userName);
this._accountsService = AccountsService.UserManager.get_default();
this._accountsService.list_users();

let userBox = new St.BoxLayout({
style_class: 'polkit-dialog-user-layout',
Expand All @@ -83,22 +129,61 @@ var AuthenticationDialog = GObject.registerClass({
});
bodyContent.add_child(userBox);

this._userAvatar = new UserWidget.Avatar(this._user, {
iconSize: DIALOG_ICON_SIZE,
this._userCombo = new St.Button({
style_class: 'polkit-dialog-user-combo',
});
this._userAvatar.x_align = Clutter.ActorAlign.CENTER;
userBox.add(this._userAvatar, { x_fill: false });
this._userCombo.connect('clicked', this._onUserComboClicked.bind(this));

const menuManager = new PopupMenu.PopupMenuManager({ actor: this._userCombo });
this._menu = new PopupMenu.PopupMenu(this._userCombo, St.Side.TOP);
Main.uiGroup.add_actor(this._menu.actor);
this._menu.actor.hide();
menuManager.addMenu(this._menu);

// Collect all available users and populate the menu
for (const name of userNames) {
let adminUser = new AdminUser(this._accountsService.get_user(name));
this._adminUsers.push(adminUser);

userBox.add(adminUser.avatar, { x_fill: false });

if (adminUser.realName !== null) {
const realName = adminUser.realName;
const userName = adminUser.userName;
const item = new PopupMenu.PopupMenuItem(`${realName} (${userName})`);
item.connect('activate', () => {
this._user = adminUser;
this._updateUser();
this._wasDismissed = true;
this.performAuthentication();
});
this._menu.addMenuItem(item);
}
}

this._userLabel = new St.Label({
style_class: userName === 'root'
? 'polkit-dialog-user-root-label'
: 'polkit-dialog-user-label',
// If the current user is an admin, set the current user
// and hide the combo
let userFound = false;
const currentUser = GLib.get_user_name();
this._adminUsers.forEach(user => {
if (user.userName === currentUser) {
this._user = user;
this._updateUser();
this._userCombo.reactive = false;
userFound = true;
}
});

if (userName === 'root')
this._userLabel.text = _('Administrator');
// If the current user is not an admin, set the first user
// as the active one. If there is more than a single user,
// show the combo
if (!userFound) {
this._user = this._adminUsers[0];
this._updateUser();
this._userCombo.reactive = userNames.length > 1;
}

userBox.add_child(this._userLabel);
userBox.add(this._userCombo, { x_fill: false });

let passwordBox = new St.BoxLayout({
style_class: 'prompt-dialog-password-layout',
Expand Down Expand Up @@ -161,7 +246,7 @@ var AuthenticationDialog = GObject.registerClass({
this._cancelButton = this.addButton({
label: _("Cancel"),
action: this.cancel.bind(this),
key: Clutter.Escape
key: Clutter.KEY_Escape
});
this._okButton = this.addButton({
label: _("Authenticate"),
Expand All @@ -180,15 +265,26 @@ var AuthenticationDialog = GObject.registerClass({
this.contentLayout.add_child(bodyContent);

this._doneEmitted = false;
}

this._identityToAuth = Polkit.UnixUser.new_for_name(userName);
this._cookie = cookie;
_onUserComboClicked() {
this._menu.toggle();
}

this._userLoadedId = this._user.connect('notify::is-loaded',
this._onUserChanged.bind(this));
this._userChangedId = this._user.connect('changed',
this._onUserChanged.bind(this));
this._onUserChanged();
_updateUser() {
global.log("Updating user");
this._adminUsers.forEach(user => {
if (user != this._user) {
user.avatar.visible = false;
} else {
user.avatar.visible = true;
this._userCombo.set_label(this._user.realName);
this._identityToAuth = Polkit.UnixUser.new_for_name(user.userName);
}
});

if (this._errorMessageLabel)
this._errorMessageLabel.set_text("");
}

performAuthentication() {
Expand Down Expand Up @@ -281,6 +377,8 @@ var AuthenticationDialog = GObject.registerClass({
Util.wiggle(this._passwordEntry);
}

this._wasDismissed = false;

/* Try and authenticate again */
this.performAuthentication();
}
Expand Down Expand Up @@ -362,19 +460,6 @@ var AuthenticationDialog = GObject.registerClass({
}
}

_onUserChanged() {
if (!this._user.is_loaded)
return;

let userName = this._user.get_user_name();
let realName = this._user.get_real_name();

if (userName !== 'root')
this._userLabel.set_text(realName);

this._userAvatar.update();
}

cancel() {
this._wasDismissed = true;
this.close(global.get_current_time());
Expand All @@ -386,11 +471,10 @@ var AuthenticationDialog = GObject.registerClass({
GLib.source_remove(this._sessionRequestTimeoutId);
this._sessionRequestTimeoutId = 0;

if (this._user) {
this._user.disconnect(this._userLoadedId);
this._user.disconnect(this._userChangedId);
this._user = null;
}
this._adminUsers.forEach(user => {
user.destroy();
});
this._adminUsers = [];

this._destroySession();
}
Expand Down

0 comments on commit 56808eb

Please sign in to comment.