Skip to content

Commit

Permalink
polkitDialog: Support multi-user selection (#12541)
Browse files Browse the repository at this point in the history
Only show user choices if there is more than one available admin user.
With this support added, enable the dialog by default.
  • Loading branch information
JosephMcc authored Dec 2, 2024
1 parent 1604bc8 commit 9945a8a
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 58 deletions.
7 changes: 6 additions & 1 deletion data/theme/cinnamon-sass/widgets/_base.scss
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,13 @@ StScrollBar {
// user avatar

.user-icon {
background-size: contain;
border-radius: 99px;
color: $fg_color;
border: none;
border-color: transparent;
background-color: transparentize($fg_color, 0.95);

& StIcon { padding: $base_padding * 2; }

&.user-avatar {
border: 1px solid transparentize($fg_color, 0.5);
Expand Down
18 changes: 14 additions & 4 deletions data/theme/cinnamon-sass/widgets/_dialogs.scss
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@
// password or authentication dialog

.prompt-dialog {
width: 28em;
width: 26em;

.dialog-content-box {
spacing: $base_margin * 4;
Expand Down Expand Up @@ -156,11 +156,21 @@

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

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

border-radius: $base_border_radius;
padding: $base_padding $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
182 changes: 132 additions & 50 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 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,59 @@ 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
let userFound = false;
const currentUser = GLib.get_user_name();
this._adminUsers.forEach(user => {
if (user.userName === currentUser) {
this._user = user;
this._updateUser();
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 +244,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 +263,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 +375,8 @@ var AuthenticationDialog = GObject.registerClass({
Util.wiggle(this._passwordEntry);
}

this._wasDismissed = false;

/* Try and authenticate again */
this.performAuthentication();
}
Expand Down Expand Up @@ -362,19 +458,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 +469,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 9945a8a

Please sign in to comment.