Fix to use one-time Usermin login URLs for switches

ⓘ Replace the legacy Usermin switch cookie/restart flow with a short-lived `session_login.cgi` handoff, while keeping the old API as a compatibility wrapper.

https://forum.virtualmin.com/t/usermin-2-550-login-from-the-virtualmin-edit-users-page-stopped-working/137491/37?u=ilia
This commit is contained in:
Ilia Ross
2026-07-08 00:39:47 +02:00
parent eadd47e886
commit 113846d243
5 changed files with 84 additions and 36 deletions
+1 -1
View File
@@ -474,7 +474,7 @@ else {
&foreign_require("usermin", "usermin-lib.pl");
local %uminiserv;
&usermin::get_usermin_miniserv_config(\%uminiserv);
if ($uminiserv{'session'}) {
if (&usermin::can_create_usermin_login_url(\%uminiserv)) {
push(@buts, [ "switch", $text{'uedit_swit'} ]);
}
}
+1 -1
View File
@@ -624,7 +624,7 @@ if ($n ne "") {
&foreign_require("usermin", "usermin-lib.pl");
local %uminiserv;
&usermin::get_usermin_miniserv_config(\%uminiserv);
if ($uminiserv{'session'}) {
if (&usermin::can_create_usermin_login_url(\%uminiserv)) {
push(@buts, [ "switch", $text{'uedit_swit'}, undef, 0,
"onClick='form.target=\"_blank\"'" ]);
}
+2
View File
@@ -433,6 +433,8 @@ clone_ecopy=failed to copy config : $1
clone_desc=Clone of $1
sessions_title=Current Login Sessions
sessions_ecannot=You are not allowed to manage Usermin login sessions
switch_esession=Failed to create a Usermin login session
sessions_desc=Current Usermin session logins are listed below. To cancel an existing session and force the user to login again, click on its session ID.
sessions_id=Session ID
sessions_user=Unix user
+2 -3
View File
@@ -3,10 +3,9 @@
require './usermin-lib.pl';
&ReadParse();
$access{'sessions'} || &error($text{'switch_euser'});
$access{'sessions'} || &error($text{'sessions_ecannot'});
($cookie, $url) = &switch_to_usermin_user($in{'user'});
print "Set-Cookie: $cookie\n";
$url = &create_usermin_login_url($in{'user'});
&redirect($url);
&webmin_log("switch", undef, $in{'user'});
+75 -28
View File
@@ -930,47 +930,84 @@ else {
return $mode;
}
=head2 switch_to_usermin_user(username)
=head2 can_create_usermin_login_url(&miniserv)
Returns a set-cookie header and redirect URL for auto-logging into Usermin
as some user.
Returns 1 if Usermin can accept a one-time login URL.
=cut
sub switch_to_usermin_user
sub can_create_usermin_login_url
{
my ($user) = @_;
my ($miniserv) = @_;
return $miniserv->{'session'} &&
defined(&acl::open_session_db) &&
defined(&acl::generate_random_session_id) &&
defined(&reload_usermin_miniserv);
}
# Stop Usermin first, so that the DBM can be safely written
=head2 create_usermin_login_url(username, [lifetime])
Creates a one-time URL that logs into Usermin as some user.
=cut
sub create_usermin_login_url
{
my ($user, $lifetime) = @_;
my %miniserv;
&get_usermin_miniserv_config(\%miniserv);
my $stopped;
if (&check_pid_file($miniserv{'pidfile'})) {
&stop_usermin();
$stopped = 1;
&can_create_usermin_login_url(\%miniserv) ||
&error($text{'switch_esession'});
&check_pid_file($miniserv{'pidfile'}) ||
&error($text{'switch_esession'});
$lifetime ||= 5*60;
my $sid = &create_usermin_login_session(\%miniserv, "-".$user,
$lifetime);
$sid || &error($text{'switch_esession'});
# Make sure the running Usermin process re-opens the session DBM, without
# stopping the service or racing its PID file re-creation.
&reload_usermin_miniserv();
my $url = &get_usermin_login_base_url(\%miniserv);
$url =~ s/\/+$//;
return $url."/session_login.cgi?session=".&urlize($sid);
}
# Generate a session ID and set it in the DB
&acl::open_session_db(\%miniserv);
&seed_random();
=head2 create_usermin_login_session(&miniserv, username, lifetime)
Creates a one-time Usermin session token for the current browser.
=cut
sub create_usermin_login_session
{
my ($miniserv, $user, $lifetime) = @_;
return if (&is_readonly_mode());
my $sid = &acl::generate_random_session_id();
return if (!$sid);
my $ip = $ENV{'REMOTE_ADDR'} || "127.0.0.1";
my $now = time();
my $sid = int(rand()*$now);
$acl::sessiondb{$sid} = "$user $now $ENV{'REMOTE_ADDR'}";
&acl::open_session_db($miniserv);
$acl::sessiondb{$sid} = "$user $now $ip".($lifetime ? " ".$lifetime : "");
dbmclose(%acl::sessiondb);
if ($stopped) {
&start_usermin();
return $sid;
}
&reload_usermin_miniserv();
=head2 get_usermin_login_base_url(&miniserv)
Returns the base URL to use for browser handoff to Usermin.
=cut
sub get_usermin_login_base_url
{
my ($miniserv) = @_;
return $miniserv->{'redirect_url'} if ($miniserv->{'redirect_url'});
eval "use Net::SSLeay";
if ($@) {
$miniserv{'ssl'} = 0;
$miniserv->{'ssl'} = 0;
}
my $ssl = $miniserv{'ssl'} || $miniserv{'inetd_ssl'};
my $sec = $ssl ? "; secure" : "";
my $sidname = $miniserv{'sidname'} || 'sid';
my $cookie = "$sidname=$sid; path=/$sec";
my $ssl = $miniserv->{'ssl'} || $miniserv->{'inetd_ssl'};
# Work out redirect host
my @sockets = &webmin::get_miniserv_sockets(\%miniserv);
my @sockets = &webmin::get_miniserv_sockets($miniserv);
my ($host, $port, $url);
my %uconfig;
&get_usermin_config(\%uconfig);
@@ -990,11 +1027,21 @@ else {
$host =~ s/:.*//;
}
}
$port ||= $uconfig{'port'} || $miniserv{'port'};
$port ||= $uconfig{'port'} || $miniserv->{'port'};
$url = ($ssl ? "https://" : "http://").$host.":".$port."/";
$url = $miniserv{'redirect_url'}
if ($miniserv{'redirect_url'});
return ($cookie, $url);
return $url;
}
=head2 switch_to_usermin_user(username)
Returns a redirect URL for auto-logging into Usermin as some user.
=cut
sub switch_to_usermin_user
{
my ($user) = @_;
my $url = &create_usermin_login_url($user);
return wantarray ? ("", $url) : $url;
}
=head2 get_usermin_email_url([module], [cgi], [force-default], [force-host])