summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbugreport%peshkin.net <>2005-10-14 08:58:24 +0200
committerbugreport%peshkin.net <>2005-10-14 08:58:24 +0200
commita23da324a647296a31436631b958bc3443ceaaf2 (patch)
treefe960b4a3e6d8da50b3b6a0db5407cdefa461029
parent4587cba89586ff3e00ed863748857ecf56a41532 (diff)
downloadbugzilla-a23da324a647296a31436631b958bc3443ceaaf2.tar.gz
bugzilla-a23da324a647296a31436631b958bc3443ceaaf2.tar.xz
Bug 204498 Add su (setuser) function
Patch by A. Karl Kornel <karl@kornel.name> r=joel, a=justdave
-rw-r--r--Bugzilla.pm76
-rw-r--r--Bugzilla/Error.pm1
-rw-r--r--Bugzilla/Template.pm5
-rwxr-xr-xchecksetup.pl22
-rw-r--r--docs/xml/administration.xml37
-rwxr-xr-xrelogin.cgi198
-rw-r--r--template/en/default/admin/sudo.html.tmpl84
-rw-r--r--template/en/default/admin/users/userdata.html.tmpl4
-rw-r--r--template/en/default/global/messages.html.tmpl11
-rw-r--r--template/en/default/global/useful-links.html.tmpl8
-rw-r--r--template/en/default/global/user-error.html.tmpl15
11 files changed, 443 insertions, 18 deletions
diff --git a/Bugzilla.pm b/Bugzilla.pm
index 183a3227c..a86e799eb 100644
--- a/Bugzilla.pm
+++ b/Bugzilla.pm
@@ -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>
diff --git a/Bugzilla/Error.pm b/Bugzilla/Error.pm
index 0f4caf274..d23e68d78 100644
--- a/Bugzilla/Error.pm
+++ b/Bugzilla/Error.pm
@@ -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;
diff --git a/Bugzilla/Template.pm b/Bugzilla/Template.pm
index a4b8084d0..b1646dbf0 100644
--- a/Bugzilla/Template.pm
+++ b/Bugzilla/Template.pm
@@ -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,
diff --git a/checksetup.pl b/checksetup.pl
index 225a3341e..f21523e5b 100755
--- a/checksetup.pl
+++ b/checksetup.pl
@@ -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
###########################################################################
diff --git a/docs/xml/administration.xml b/docs/xml/administration.xml
index 16223ce1e..8c79e6fb7 100644
--- a/docs/xml/administration.xml
+++ b/docs/xml/administration.xml
@@ -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>
diff --git a/relogin.cgi b/relogin.cgi
index 1d682965d..58ac84769 100755
--- a/relogin.cgi
+++ b/relogin.cgi
@@ -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());
diff --git a/template/en/default/admin/sudo.html.tmpl b/template/en/default/admin/sudo.html.tmpl
new file mode 100644
index 000000000..ea4f0f38e
--- /dev/null
+++ b/template/en/default/admin/sudo.html.tmpl
@@ -0,0 +1,84 @@
+[%# 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 %]
diff --git a/template/en/default/admin/users/userdata.html.tmpl b/template/en/default/admin/users/userdata.html.tmpl
index 43ee627f1..ab2e9de62 100644
--- a/template/en/default/admin/users/userdata.html.tmpl
+++ b/template/en/default/admin/users/userdata.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 %]
diff --git a/template/en/default/global/messages.html.tmpl b/template/en/default/global/messages.html.tmpl
index 7a33265a5..6730e8835 100644
--- a/template/en/default/global/messages.html.tmpl
+++ b/template/en/default/global/messages.html.tmpl
@@ -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 %] /
diff --git a/template/en/default/global/useful-links.html.tmpl b/template/en/default/global/useful-links.html.tmpl
index 2ac89f91c..ef7f3d19f 100644
--- a/template/en/default/global/useful-links.html.tmpl
+++ b/template/en/default/global/useful-links.html.tmpl
@@ -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>
diff --git a/template/en/default/global/user-error.html.tmpl b/template/en/default/global/user-error.html.tmpl
index 22d7c8dcd..56fedbed3 100644
--- a/template/en/default/global/user-error.html.tmpl
+++ b/template/en/default/global/user-error.html.tmpl
@@ -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