Commit a23da324 authored by bugreport%peshkin.net's avatar bugreport%peshkin.net

Bug 204498 Add su (setuser) function

Patch by A. Karl Kornel <karl@kornel.name> r=joel, a=justdave
parent 4587cba8
......@@ -19,7 +19,7 @@
#
# Contributor(s): Bradley Baetz <bbaetz@student.usyd.edu.au>
# Erik Stambaugh <erik@dasbistro.com>
#
# A. Karl Kornel <karl@kornel.name>
package Bugzilla;
......@@ -132,9 +132,60 @@ sub user {
return $_user;
}
my $_sudoer;
sub sudoer {
my $class = shift;
return $_sudoer;
}
sub sudo_request {
my $class = shift;
my $new_user = shift;
my $new_sudoer = shift;
$_user = $new_user;
$_sudoer = $new_sudoer;
$::userid = $new_user->id;
# NOTE: If you want to log the start of an sudo session, do it here.
return;
}
sub login {
my ($class, $type) = @_;
$_user = Bugzilla::Auth::Login::WWW->login($type);
my $authenticated_user = Bugzilla::Auth::Login::WWW->login($type);
# At this point, we now know if a real person is logged in.
# We must now check to see if an sudo session is in progress.
# For a session to be in progress, the following must be true:
# 1: There must be a logged in user
# 2: That user must be in the 'bz_sudoer' group
# 3: There must be a valid value in the 'sudo' cookie
# 4: A Bugzilla::User object must exist for the given cookie value
# 5: That user must NOT be in the 'bz_sudo_protect' group
my $sudo_cookie = $class->cgi->cookie('sudo');
detaint_natural($sudo_cookie) if defined($sudo_cookie);
my $sudo_target;
$sudo_target = new Bugzilla::User($sudo_cookie) if defined($sudo_cookie);
if (defined($authenticated_user) &&
$authenticated_user->in_group('bz_sudoers') &&
defined($sudo_cookie) &&
defined($sudo_target) &&
!($sudo_target->in_group('bz_sudo_protect'))
)
{
$_user = $sudo_target;
$_sudoer = $authenticated_user;
$::userid = $sudo_target->id;
# NOTE: If you want to do any special logging, do it here.
}
else {
$_user = $authenticated_user;
}
return $_user;
}
sub logout {
......@@ -164,6 +215,7 @@ sub logout_user_by_id {
# hack that invalidates credentials for a single request
sub logout_request {
undef $_user;
undef $_sudoer;
# XXX clean this up eventually
$::userid = 0;
# We can't delete from $cgi->cookie, so logincookie data will remain
......@@ -332,8 +384,24 @@ method for those scripts/templates which are only use via CGI, though.
=item C<user>
The current C<Bugzilla::User>. C<undef> if there is no currently logged in user
or if the login code has not yet been run.
C<undef> if there is no currently logged in user or if the login code has not
yet been run. If an sudo session is in progress, the C<Bugzilla::User>
corresponding to the person who is being impersonated. If no session is in
progress, the current C<Bugzilla::User>.
=item C<sudoer>
C<undef> if there is no currently logged in user, the currently logged in user
is not in the I<sudoer> group, or there is no session in progress. If an sudo
session is in progress, returns the C<Bugzilla::User> object corresponding to
the person who logged in and initiated the session. If no session is in
progress, returns the C<Bugzilla::User> object corresponding to the currently
logged in user.
=item C<sudo_request>
This begins an sudo session for the current request. It is meant to be
used when a session has just started. For normal use, sudo access should
normally be set at login time.
=item C<login>
......
......@@ -51,6 +51,7 @@ sub _throw_error {
$mesg .= "$name $error ";
$mesg .= "$ENV{REMOTE_ADDR} " if $ENV{REMOTE_ADDR};
$mesg .= Bugzilla->user->login;
$mesg .= (' actually ' . Bugzilla->sudoer->login) if Bugzilla->sudoer;
$mesg .= "\n";
my %params = Bugzilla->cgi->Vars;
$Data::Dumper::Useqq = 1;
......
......@@ -497,8 +497,13 @@ sub create {
'lsearch' => \&Bugzilla::Util::lsearch,
# Currently logged in user, if any
# If an sudo session is in progress, this is the user we're faking
'user' => sub { return Bugzilla->user; },
# If an sudo session is in progress, this is the user who
# started the session.
'sudoer' => sub { return Bugzilla->sudoer; },
# UserInGroup. Deprecated - use the user.* functions instead
'UserInGroup' => \&Bugzilla::User::UserInGroup,
......
......@@ -33,6 +33,7 @@
# Max Kanat-Alexander <mkanat@bugzilla.org>
# Joel Peshkin <bugreport@peshkin.net>
# Lance Larsh <lance.larsh@oracle.com>
# A. Karl Kornel <karl@kornel.name>
#
#
#
......@@ -4131,6 +4132,27 @@ while (my ($uid, $login, $gid, $rexp, $present) = $sth->fetchrow_array()) {
}
}
# 2005-10-10 karl@kornel.name -- Bug 204498
if (!GroupDoesExist('bz_sudoers')) {
my $sudoers_group = AddGroup('bz_sudoers',
'Can perform actions as other users');
my $sudo_protect_group = AddGroup('bz_sudo_protect',
'Can not be impersonated by other users');
my ($admin_group) = $dbh->selectrow_array('SELECT id FROM groups
WHERE name = ?', undef, 'admin');
# Admins should be given sudo access
# Everyone in sudo should be in sudo_protect
# Admins can grant membership in both groups
my $sth = $dbh->prepare('INSERT INTO group_group_map
(member_id, grantor_id, grant_type)
VALUES (?, ?, ?)');
$sth->execute($admin_group, $sudoers_group, GROUP_MEMBERSHIP);
$sth->execute($sudoers_group, $sudo_protect_group, GROUP_MEMBERSHIP);
$sth->execute($admin_group, $sudoers_group, GROUP_BLESS);
$sth->execute($admin_group, $sudo_protect_group, GROUP_BLESS);
}
###########################################################################
# Create --SETTINGS-- users can adjust
###########################################################################
......
......@@ -520,6 +520,43 @@
</listitem>
</itemizedlist>
</section>
<section id="impersonatingusers">
<title>Impersonating Users</title>
<para>
There may be times when an administrator would like to do something as
another user. The <command>sudo</command> feature may be used to do
this.
</para>
<note>
<para>
To use the sudo feature, you must be in the
<emphasis>bz_sudoers</emphasis> group. By default, all
administrators are in this group.</para>
</note>
<para>
If you have access to use this feature, you should notice a link
next to your login name (in the footer) titled "sudo". Click on the
link. This will take you to a page where you will see a description of
the feature and instructions on how to use it. After reading the text,
simply enter the login of the user you would like to impersonate and
press the button.</para>
<para>
As long as you are using this feature, everything you do will be done
as if you were logged in as the user you are impersonating.</para>
<warning>
<para>
The user you are impersonating will not be told about what you are
doing. If you do anything that results in mail being sent, that
mail will appear to be from the user you are impersonating. You
should be extremely careful while using this feature.</para>
</warning>
</section>
</section>
</section>
......
......@@ -20,29 +20,203 @@
#
# Contributor(s): Terry Weissman <terry@mozilla.org>
# Gervase Markham <gerv@gerv.net>
# A. Karl Kornel <karl@kornel.name>
use strict;
use lib qw(.);
use Bugzilla;
use Bugzilla::Auth::Login::WWW;
use Bugzilla::CGI;
use Bugzilla::Constants;
use Bugzilla::Error;
# We don't want to remove a random logincookie from the db, so
# call Bugzilla->login(). If we're logged in after this, then
# the logincookie must be correct
Bugzilla->login(LOGIN_OPTIONAL);
Bugzilla->logout();
use Bugzilla::User;
use Bugzilla::Util;
use Date::Format;
my $template = Bugzilla->template;
my $cgi = Bugzilla->cgi;
print $cgi->header();
my $action = $cgi->param('action') || 'logout';
my $vars = {};
$vars->{'message'} = "logged_out";
$template->process("global/message.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
my $target;
# sudo: Display the sudo information & login page
if ($action eq 'sudo') {
# We must have a logged-in user to do this
# That user must be in the 'bz_sudoers' group
my $user = Bugzilla->login(LOGIN_REQUIRED);
unless ($user->in_group('bz_sudoers')) {
ThrowUserError('auth_failure', { group => 'bz_sudoers',
action => 'begin',
object => 'sudo_session' }
);
}
# Do not try to start a new session if one is already in progress!
if (defined(Bugzilla->sudoer)) {
ThrowUserError('sudo-in-progress', { target => $user->login });
}
# We may have been given a value to put into the field
# Don't pass it through unless it's actually a user
# Could the default value be protected? Maybe, but we will save the
# disappointment for later!
if (defined($cgi->param('target_login')) &&
Bugzilla::User::login_to_id($cgi->param('target_login')) != 0)
{
$vars->{'target_login_default'} = $cgi->param('target_login');
}
# Show the sudo page
$vars->{'will_logout'} = 1 if Bugzilla::Auth::Login::WWW->can_logout;
$target = 'admin/sudo.html.tmpl';
}
# transition-sudo: Validate target, logout user, and redirect for session start
elsif ($action eq 'sudo-transition') {
# Get the current user
my $user = Bugzilla->login(LOGIN_REQUIRED);
unless ($user->in_group('bz_sudoers')) {
ThrowUserError('auth_failure', { group => 'bz_sudoers',
action => 'begin',
object => 'sudo_session' }
);
}
# Get & verify the target user (the user who we will be impersonating)
unless (defined($cgi->param('target_login')) &&
Bugzilla::User::login_to_id($cgi->param('target_login')) != 0)
{
ThrowUserError('invalid_username',
{ 'name' => $cgi->param('target_login') }
);
}
my $target_user = new Bugzilla::User(
Bugzilla::User::login_to_id($cgi->param('target_login'))
);
unless (defined($target_user) &&
$target_user->id != 0)
{
ThrowUserError('invalid_username',
{ 'name' => $cgi->param('target_login') }
);
}
unless (defined($cgi->param('target_login')) &&
$target_user->id != 0)
{
ThrowUserError('invalid_username',
{ 'name' => $cgi->param('target_login') }
);
}
if ($target_user->in_group('bz_sudo_protect')) {
ThrowUserError('sudo_protected', { login => $target_user->login });
}
# Log out and Redirect user to the new page
Bugzilla->logout();
$target = 'relogin.cgi';
print $cgi->redirect($target . '?action=begin-sudo&target_login=' .
url_quote($target_user->login));
exit;
}
# begin-sudo: Confirm login and start sudo session
elsif ($action eq 'begin-sudo') {
# We must have a logged-in user to do this
# That user must be in the 'bz_sudoers' group
my $user = Bugzilla->login(LOGIN_REQUIRED);
unless ($user->in_group('bz_sudoers')) {
ThrowUserError('auth_failure', { group => 'bz_sudoers',
action => 'begin',
object => 'sudo_session' }
);
}
# Get & verify the target user (the user who we will be impersonating)
unless (defined($cgi->param('target_login')) &&
Bugzilla::User::login_to_id($cgi->param('target_login')) != 0)
{
ThrowUserError('invalid_username',
{ 'name' => $cgi->param('target_login') }
);
}
my $target_user = new Bugzilla::User(
Bugzilla::User::login_to_id($cgi->param('target_login'))
);
unless (defined($target_user) &&
$target_user->id != 0)
{
ThrowUserError('invalid_username',
{ 'name' => $cgi->param('target_login') }
);
}
unless (defined($cgi->param('target_login')) &&
$target_user->id != 0)
{
ThrowUserError('invalid_username',
{ 'name' => $cgi->param('target_login') }
);
}
if ($target_user->in_group('bz_sudo_protect')) {
ThrowUserError('sudo_protected', { login => $target_user->login });
}
# Calculate the session expiry time (T + 6 hours)
my $time_string = time2str('%a, %d-%b-%Y %T %Z', time+(6*60*60), 'GMT');
exit;
# For future sessions, store the unique ID of the target user
Bugzilla->cgi->send_cookie('-name' => 'sudo',
'-expires' => $time_string,
'-value' => $target_user->id
);
# For the present, change the values of Bugzilla::user & Bugzilla::sudoer
Bugzilla->sudo_request($target_user, Bugzilla->user);
# NOTE: If you want to log the start of an sudo session, do it here.
$vars->{'message'} = 'sudo_started';
$vars->{'target'} = $target_user->login;
$target = 'global/message.html.tmpl';
}
# end-sudo: End the current sudo session (if one is in progress)
elsif ($action eq 'end-sudo') {
# Regardless of our state, delete the sudo cookie if it exists
$cgi->remove_cookie('sudo');
# Are we in an sudo session?
Bugzilla->login(LOGIN_OPTIONAL);
my $sudoer = Bugzilla->sudoer;
if (defined($sudoer)) {
Bugzilla->logout_request();
Bugzilla->sudo_request($sudoer, undef);
}
# NOTE: If you want to log the end of an sudo session, so it here.
$vars->{'message'} = 'sudo_ended';
$target = 'global/message.html.tmpl';
}
# Log out the currently logged-in user (this used to be the only thing this did)
elsif ($action eq 'logout') {
# We don't want to remove a random logincookie from the db, so
# call Bugzilla->login(). If we're logged in after this, then
# the logincookie must be correct
Bugzilla->login(LOGIN_OPTIONAL);
$cgi->remove_cookie('sudo');
Bugzilla->logout();
my $template = Bugzilla->template;
my $cgi = Bugzilla->cgi;
print $cgi->header();
$vars->{'message'} = "logged_out";
$target = 'global/message.html.tmpl';
}
# Display the template
print $cgi->header();
$template->process($target, $vars)
|| ThrowTemplateError($template->error());
[%# 1.0@bugzilla.org %]
[%# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 2005 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): A. Karl Kornel <karl@kornel.name>
#%]
[% PROCESS global/variables.none.tmpl %]
[% PROCESS global/header.html.tmpl
title = "Begin sudo session"
style_urls = ['skins/standard/admin.css']
%]
[% DEFAULT target_login = "" %]
<p>
The <b>sudo</b> feature of [% terms.Bugzilla %] allows you to impersonate a
user for a short time While an sudo session is in progress, every action you
perform will be taking place as if you had logged in as the user whom will be
impersonating.
</p>
<p class="areyoureallyreallysure">
This is a very powerful feature; you should be very careful while using it.
Your actions may be logged more carefully than normal.
</p>
<form action="relogin.cgi" method="POST">
<p>
To begin,
[% IF Param('usemenuforusers') %]
select
[% ELSE %]
enter the login of
[% END %]
the <u>u</u>ser to impersonate:
[% INCLUDE global/userselect.html.tmpl
name => "target_login"
value => "$target_login_default"
accesskey => "u"
size => 30
multiple => 5
%]
</p>
[% IF !Param('usemenuforusers') %]
<p>
The username must be entered exactly. No matching will be performed.
</p>
[% END %]
<p>
Next, click the button to begin the session:
<input type="submit" value="Begin Session">
<input type="hidden" name="action" value="sudo-transition">
</p>
[% IF will_logout %]
<p>
When you press the button, you may be logged out and asked to log in
again. This is done for two reasons. First of all, it is done to reduce
the chances of someone doing large amounts of damage using your
already-logged-in account. Second, it is there to force you to take the
time to consider if you really need to use this feature.
</p>
[% END %]
</form>
[% PROCESS global/footer.html.tmpl %]
......@@ -29,7 +29,9 @@
id="login" value="[% otheruser.login FILTER html %]" />
[% IF editform %]
<input type="hidden" name="loginold"
value="[% otheruser.login FILTER html %]" />
value="[% otheruser.login FILTER html %]" /><br />
<a href="relogin.cgi?action=sudo&target_login=
[%- otheruser.login FILTER html %]">Impersonate this user</a>
[% END %]
[% ELSE %]
[% otheruser.login FILTER html %]
......
......@@ -237,6 +237,17 @@
set to zero automatically as part of marking this [% terms.bug %]
as either RESOLVED or CLOSED.
[% ELSIF message_tag == "sudo_started" %]
[% title = "Sudo session started" %]
The sudo session has been started. For the next 6 hours, or until you
end the session, everything you do you do as the user you are
impersonating ([% target FILTER html %]).
[% ELSIF message_tag == "sudo_ended" %]
[% title = "Sudo session complete" %]
The sudo session has been ended. From this point forward, everything you
do you do as yourself.
[% ELSIF message_tag == "series_created" %]
[% title = "Series Created" %]
The series <em>[% series.category FILTER html %] /
......
......@@ -62,7 +62,13 @@
[% ELSE %]
| Logged&nbsp;in&nbsp;as&nbsp;
[% END %]
[% user.login FILTER html %]
[% IF sudoer %]
[% sudoer.login FILTER html %] (<b>impersonating
[% user.login FILTER html %]</b>
<a href="relogin.cgi?action=end-sudo">end session</a>)
[% ELSE %]
[% user.login FILTER html %]
[% END %]
[% ELSE %]
[% IF Param('createemailregexp') %]
| <a href="createaccount.cgi">New&nbsp;Account</a>
......
......@@ -128,6 +128,8 @@
access
[% ELSIF action == "add" %]
add new
[% ELSIF action == "begin" %]
begin
[% ELSIF action == "modify" %]
modify
[% ELSIF action == "delete" %]
......@@ -156,6 +158,8 @@
components
[% ELSIF object == "flagtypes" %]
flag types
[% ELSIF object == "group_access" %]
group access
[% ELSIF object == "groups" %]
groups
[% ELSIF object == "keywords" %]
......@@ -174,6 +178,8 @@
whine reports
[% ELSIF object == "sanity_check" %]
a sanity check
[% ELSIF object == "sudo_session" %]
an sudo session
[% ELSIF object == "timetracking_summaries" %]
time-tracking summary reports
[% ELSIF object == "user" %]
......@@ -1111,6 +1117,15 @@
[% END %]
[% END %]
[% ELSIF error == "sudo_in_progress" %]
[% title = "Session In Progress" %]
An sudo session (impersonating [% target FILTER html %]) is in progress.
End that session (using the link in the footer) before starting a new one.
[% ELSIF error == "sudo_protected" %]
[% title = "User Protected" %]
The user [% login FILTER html %] may not be impersonated by sudoers.
[% ELSIF error == "too_many_votes_for_bug" %]
[% title = "Illegal Vote" %]
You may only use at most [% max FILTER html %] votes for a single
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment