]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
Bug 204498 Add su (setuser) function
authorbugreport%peshkin.net <>
Fri, 14 Oct 2005 06:58:24 +0000 (06:58 +0000)
committerbugreport%peshkin.net <>
Fri, 14 Oct 2005 06:58:24 +0000 (06:58 +0000)
Patch by A. Karl Kornel <karl@kornel.name>
r=joel, a=justdave

Bugzilla.pm
Bugzilla/Error.pm
Bugzilla/Template.pm
checksetup.pl
docs/xml/administration.xml
relogin.cgi
template/en/default/admin/sudo.html.tmpl [new file with mode: 0644]
template/en/default/admin/users/userdata.html.tmpl
template/en/default/global/messages.html.tmpl
template/en/default/global/useful-links.html.tmpl
template/en/default/global/user-error.html.tmpl

index 183a3227c236da792eb5a5e5674a72ffee2dac48..a86e799eba051050ab88d69c877d63adb9b44020 100644 (file)
@@ -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>
 
index 0f4caf27436376592f20fce5b5afdcf6b91fdea9..d23e68d78ad9d4d34cd4d9bf26d63cd266adb48f 100644 (file)
@@ -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;
index a4b8084d0692558d9e1254e2d1da74af9600acb6..b1646dbf0c0027fc601f6b7faf8f1898887f76dc 100644 (file)
@@ -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,
 
index 225a3341e44c69aba3d399d7b428bceb3c281b5e..f21523e5beaf30e5d444b4defdfdbf6ddc0cd49f 100755 (executable)
@@ -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
 ###########################################################################
index 16223ce1eed2704ca91e4dd94e02dd4cdf15408d..8c79e6fb77121fdfb05e3f176ff2a3abdaa789c5 100644 (file)
           </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>
 
index 1d682965db0a613dedf97df167a43b84710d030c..58ac84769a0d850f8f270821851276f163997436 100755 (executable)
 #
 # 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 (file)
index 0000000..ea4f0f3
--- /dev/null
@@ -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 %]
index 43ee627f13934556fb1f99c21d45b884190e505b..ab2e9de62927dbaa3993b02e48bc1dbbebf05eea 100644 (file)
@@ -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 %]
index 7a33265a5e6155a88b189ac55dc40eb2c8e65bd7..6730e8835f3fe01b56553eb3313cafad7a296af1 100644 (file)
     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 %] /
index 2ac89f91c6d3af5082ec62d553b88cd19cd06592..ef7f3d19f360682b9094cfcbd472e77b447daf82 100644 (file)
             [% 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>
index 22d7c8dcdc8f09522d48c80182f7ae147fddd921..56fedbed3c38f29cb0a2431748d2ae44bb716b9f 100644 (file)
       access
     [% ELSIF action == "add" %]
       add new
+    [% ELSIF action == "begin" %]
+      begin
     [% ELSIF action == "modify" %]
       modify
     [% ELSIF action == "delete" %]
       components
     [% ELSIF object == "flagtypes" %]
       flag types
+    [% ELSIF object == "group_access" %]
+      group access
     [% ELSIF object == "groups" %]
       groups
     [% ELSIF object == "keywords" %]
       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" %]
       [% 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