Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

polkitDialog: Support multi-user selection #12541

Merged
merged 1 commit into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading