From a23da324a647296a31436631b958bc3443ceaaf2 Mon Sep 17 00:00:00 2001 From: "bugreport%peshkin.net" <> Date: Fri, 14 Oct 2005 06:58:24 +0000 Subject: Bug 204498 Add su (setuser) function Patch by A. Karl Kornel r=joel, a=justdave --- Bugzilla.pm | 76 +++++++- Bugzilla/Error.pm | 1 + Bugzilla/Template.pm | 5 + checksetup.pl | 22 +++ docs/xml/administration.xml | 37 ++++ relogin.cgi | 198 +++++++++++++++++++-- template/en/default/admin/sudo.html.tmpl | 84 +++++++++ template/en/default/admin/users/userdata.html.tmpl | 4 +- template/en/default/global/messages.html.tmpl | 11 ++ template/en/default/global/useful-links.html.tmpl | 8 +- template/en/default/global/user-error.html.tmpl | 15 ++ 11 files changed, 443 insertions(+), 18 deletions(-) create mode 100644 template/en/default/admin/sudo.html.tmpl 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 # Erik Stambaugh -# +# A. Karl Kornel 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 -The current C. C if there is no currently logged in user -or if the login code has not yet been run. +C 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 +corresponding to the person who is being impersonated. If no session is in +progress, the current C. + +=item C + +C if there is no currently logged in user, the currently logged in user +is not in the I group, or there is no session in progress. If an sudo +session is in progress, returns the C object corresponding to +the person who logged in and initiated the session. If no session is in +progress, returns the C object corresponding to the currently +logged in user. + +=item C +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 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 # Joel Peshkin # Lance Larsh +# A. Karl Kornel # # # @@ -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 @@ + +
+ Impersonating Users + + + There may be times when an administrator would like to do something as + another user. The sudo feature may be used to do + this. + + + + + To use the sudo feature, you must be in the + bz_sudoers group. By default, all + administrators are in this group. + + + + 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. + + + 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. + + + + 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. + +
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 # Gervase Markham +# A. Karl Kornel 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 + #%] + +[% PROCESS global/variables.none.tmpl %] + +[% PROCESS global/header.html.tmpl + title = "Begin sudo session" + style_urls = ['skins/standard/admin.css'] + %] + +[% DEFAULT target_login = "" %] + +

+ The sudo 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. +

+ +

+ This is a very powerful feature; you should be very careful while using it. + Your actions may be logged more carefully than normal. +

+ +
+

+ To begin, + [% IF Param('usemenuforusers') %] + select + [% ELSE %] + enter the login of + [% END %] + the user to impersonate: + [% INCLUDE global/userselect.html.tmpl + name => "target_login" + value => "$target_login_default" + accesskey => "u" + size => 30 + multiple => 5 + %] +

+ + [% IF !Param('usemenuforusers') %] +

+ The username must be entered exactly. No matching will be performed. +

+ [% END %] + +

+ Next, click the button to begin the session: + + +

+ + [% IF will_logout %] +

+ 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. +

+ [% END %] +
+ +[% 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 %] + value="[% otheruser.login FILTER html %]" />
+ Impersonate this user [% 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 [% 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 in as  [% END %] - [% user.login FILTER html %] + [% IF sudoer %] + [% sudoer.login FILTER html %] (impersonating + [% user.login FILTER html %] + end session) + [% ELSE %] + [% user.login FILTER html %] + [% END %] [% ELSE %] [% IF Param('createemailregexp') %] | New Account 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 -- cgit v1.2.3-24-g4f1b