From 2a5d8de847971f7b5476ad6026fcbfcb68813354 Mon Sep 17 00:00:00 2001 From: "travis%sedsystems.ca" <> Date: Tue, 1 Mar 2005 04:41:42 +0000 Subject: Bug 119485 : Templatise editusers.cgi Patch by Marc Schumann r=GavinS, mkanat a=justdave --- editusers.cgi | 1468 ++++++++++++++++++++++++++------------------------------- 1 file changed, 681 insertions(+), 787 deletions(-) (limited to 'editusers.cgi') diff --git a/editusers.cgi b/editusers.cgi index 4c2a8a193..87a8b69bd 100755 --- a/editusers.cgi +++ b/editusers.cgi @@ -11,23 +11,9 @@ # implied. See the License for the specific language governing # rights and limitations under the License. # -# The Original Code is mozilla.org code. +# The Original Code is the Bugzilla Bug Tracking System. # -# The Initial Developer of the Original Code is Holger -# Schurig. Portions created by Holger Schurig are -# Copyright (C) 1999 Holger Schurig. All -# Rights Reserved. -# -# Contributor(s): Holger Schurig -# Dave Miller -# Joe Robins -# Dan Mosedale -# Joel Peshkin -# Erik Stambaugh -# -# Direct any questions on this source code to -# -# Holger Schurig +# Contributor(s): Marc Schumann use strict; use lib "."; @@ -35,827 +21,735 @@ use lib "."; require "CGI.pl"; require "globals.pl"; -use Bugzilla; +use vars qw( $vars ); + use Bugzilla::User; use Bugzilla::Constants; use Bugzilla::Auth; -# Shut up misguided -w warnings about "used only once". "use vars" just -# doesn't work for me. - -sub sillyness { - my $zz; - $zz = $::userid; -} - -my $editall; - - - -# TestUser: just returns if the specified user does exists -# CheckUser: same check, optionally emit an error text - -sub TestUser ($) -{ - my $user = shift; - - # does the product exist? - SendSQL("SELECT login_name - FROM profiles - WHERE login_name=" . SqlQuote($user)); - return FetchOneColumn(); -} - -sub CheckUser ($) -{ - my $user = shift; - - # do we have a user? - unless ($user) { - print "Sorry, you haven't specified a user."; - PutTrailer(); - exit; - } - - unless (TestUser $user) { - print "Sorry, user '$user' does not exist."; - PutTrailer(); - exit; - } -} +Bugzilla->login(LOGIN_REQUIRED); +my $cgi = Bugzilla->cgi(); +my $template = Bugzilla->template(); +my $dbh = Bugzilla->dbh; +my $user = Bugzilla->user(); +my $userid = $user->id(); +my $editusers = UserInGroup('editusers'); +my $action = $cgi->param('action') || 'search'; + +# Reject access if there is no sense in continuing. +$editusers + || Bugzilla->user->can_bless() + || ThrowUserError("auth_failure", {group => "editusers", + reason => "cant_bless", + action => "edit", + object => "users"}); +print Bugzilla->cgi->header(); -sub EmitElement ($$) -{ - my ($name, $value) = (@_); - $value = value_quote($value); - if ($editall) { - print qq{\n}; +$vars->{'editusers'} = $editusers; +mirrorListSelectionValues(); + +########################################################################### +if ($action eq 'search') { + # Allow to restrict the search to any group the user is allowed to bless. + $vars->{'restrictablegroups'} = groupsUserMayBless($user, 'id', 'name'); + $template->process('admin/users/search.html.tmpl', $vars) + || ThrowTemplateError($template->error()); + +########################################################################### +} elsif ($action eq 'list') { + my $matchstr = $cgi->param('matchstr'); + my $matchtype = $cgi->param('matchtype'); + my $grouprestrict = $cgi->param('grouprestrict') || '0'; + my $groupid = $cgi->param('groupid'); + my $query = 'SELECT DISTINCT userid, login_name, realname, disabledtext ' . + 'FROM profiles'; + my @bindValues; + my $nextCondition; + + if (Param('usevisibilitygroups')) { + # Show only users in visible groups. + my $visibleGroups = visibleGroupsAsString(); + $query .= qq{, user_group_map AS ugm + WHERE ugm.user_id = profiles.userid + AND ugm.isbless = 0 + AND ugm.group_id IN ($visibleGroups) + }; + $nextCondition = 'AND'; } else { - print qq{$value\n}; + if ($grouprestrict eq '1') { + $query .= ', user_group_map AS ugm'; + } + $nextCondition = 'WHERE'; } -} - -# -# Displays the form to edit a user parameters -# - -sub EmitFormElements ($$$$) -{ - my ($user_id, $user, $realname, $disabledtext) = @_; - - print " Login name:\n"; - EmitElement("user", $user); - - print "\n"; - print " Real name:\n"; - EmitElement("realname", $realname); - - if ($editall) { - print "\n"; - print " Password:\n"; - print qq| -
- (enter new password to change) - - |; - print "\n"; - - print " Disable text:\n"; - print " \n"; - print " \n"; - print "\n"; - print " If non-empty, then the account will\n"; - print "be disabled, and this text should explain why.\n"; - } - - - if($user ne "") { - print "Group Access:"; - SendSQL("SELECT groups.id, groups.name, groups.description, " . - "MAX(CASE WHEN grant_type = " . GRANT_DIRECT . " THEN 1 ELSE 0 END)," . - "MAX(CASE WHEN grant_type = " . GRANT_DERIVED . " THEN 1 ELSE 0 END)," . - "MAX(CASE WHEN grant_type = " . GRANT_REGEXP . " THEN 1 ELSE 0 END)" . - "FROM groups " . - "LEFT JOIN user_group_map " . - "ON user_group_map.group_id = groups.id " . - "AND isbless = 0 " . - "AND user_id = $user_id " . - "GROUP BY groups.name "); - if (MoreSQLData()) { - if ($editall) { - print "\n"; - print "\n\n"; - } - print "\n"; - while (MoreSQLData()) { - my ($groupid, $name, $description, $checked, $isderived, $isregexp) = FetchSQLData(); - next unless ($editall || Bugzilla->user->can_bless($name)); - PushGlobalSQLState(); - SendSQL("SELECT user_id " . - "FROM user_group_map " . - "WHERE isbless = 1 " . - "AND user_id = $user_id " . - "AND group_id = $groupid"); - my ($blchecked) = FetchSQLData() ? 1 : 0; - SendSQL("SELECT grantor_id FROM user_group_map, - group_group_map - WHERE $groupid = grantor_id - AND user_group_map.user_id = $user_id - AND user_group_map.isbless = 0 - AND group_group_map.grant_type = " . GROUP_BLESS . " - AND user_group_map.group_id = member_id"); - my $derivedbless = FetchOneColumn(); - PopGlobalSQLState(); - print "\n"; - print "\n"; - print "\n"; - if ($editall) { - $blchecked = ($blchecked) ? "CHECKED" : ""; - print "\n"; - } - $checked = ($checked) ? "CHECKED" : ""; - print "\n"; - } + # Selection by user name. + if (defined($matchtype)) { + $query .= " $nextCondition profiles.login_name "; + if ($matchtype eq 'regexp') { + $query .= $dbh->sql_regexp . ' ?'; + $matchstr = '.' unless $matchstr; + } elsif ($matchtype eq 'notregexp') { + $query .= $dbh->sql_not_regexp . ' ?'; + $matchstr = '.' unless $matchstr; + } else { # substr or unknown + $query .= 'like ?'; + $matchstr = "%$matchstr%"; } - print "
Can turn this bit on for other users
|User is a member of these groups
"; - print "[" if $derivedbless; - print ""; - print "]" if $derivedbless; - print ""; - print '[' if ($isderived); - print '*' if ($isregexp); - print ""; - print ']' if ($isderived); - print '*' if ($isregexp); - print ""; - print ucfirst($name) . ": $description
\n"; + $nextCondition = 'AND'; + # We can trick_taint because we use the value in a SELECT only, using + # a placeholder. + trick_taint($matchstr); + push(@bindValues, $matchstr); } -} - - -# -# Displays a text like "a.", "a or b.", "a, b or c.", "a, b, c or d." -# - -sub PutTrailer (@) -{ - my (@links) = ("Back to the index"); - if($editall) { - push(@links, - "add a new user"); + # Selection by group. + if ($grouprestrict eq '1') { + $query .= " $nextCondition profiles.userid = ugm.user_id " . + 'AND ugm.group_id = ?'; + # We can trick_taint because we use the value in a SELECT only, using + # a placeholder. + trick_taint($groupid); + push(@bindValues, $groupid); } - push(@links, @_); - - my $count = $#links; - my $num = 0; - print "

\n"; - foreach (@links) { - print $_; - if ($num == $count) { - print ".\n"; + $query .= ' ORDER BY profiles.login_name'; + + $vars->{'users'} = $dbh->selectall_arrayref($query, + {'Slice' => {}}, + @bindValues); + $template->process('admin/users/list.html.tmpl', $vars) + || ThrowTemplateError($template->error()); + +########################################################################### +} elsif ($action eq 'add') { + $editusers || ThrowUserError("auth_failure", {group => "editusers", + action => "add", + object => "users"}); + + $template->process('admin/users/create.html.tmpl', $vars) + || ThrowTemplateError($template->error()); + +########################################################################### +} elsif ($action eq 'new') { + $editusers || ThrowUserError("auth_failure", {group => "editusers", + action => "add", + object => "users"}); + + my $login = $cgi->param('login'); + my $password = $cgi->param('password'); + + # Cleanups + my $realname = trim($cgi->param('name') || ''); + my $disabledtext = trim($cgi->param('disabledtext') || ''); + + # Lock tables during the check+creation session. + $dbh->bz_lock_tables('profiles WRITE', + 'profiles_activity WRITE', + 'namedqueries READ', + 'whine_queries READ', + 'tokens READ'); + + # Validity checks + $login || ThrowUserError('user_login_required'); + CheckEmailSyntax($login); + is_available_username($login) || ThrowUserError('account_exists', + {'email' => $login}); + ValidatePassword($password); + + # Login and password are validated now, and realname and disabledtext + # are allowed to contain anything + trick_taint($login); + trick_taint($realname); + trick_taint($password); + trick_taint($disabledtext); + + insert_new_user($login, $realname, $password, $disabledtext); + my $userid = $dbh->bz_last_key('profiles', 'userid'); + $dbh->bz_unlock_tables(); + userDataToVars($userid); + + $vars->{'message'} = 'account_created'; + $template->process('admin/users/edit.html.tmpl', $vars) + || ThrowTemplateError($template->error()); + +########################################################################### +} elsif ($action eq 'edit') { + my $otherUser = new Bugzilla::User($cgi->param('userid')) + || ThrowCodeError('invalid_user_id', {'userid' => $cgi->param('userid')}); + my $otherUserID = $otherUser->id(); + + canSeeUser($otherUserID) + || ThrowUserError('auth_failure', {reason => "not_visible", + action => "modify", + object => "user"}); + + userDataToVars($otherUserID); + + $template->process('admin/users/edit.html.tmpl', $vars) + || ThrowTemplateError($template->error()); + +########################################################################### +} elsif ($action eq 'update') { + my $otherUser = new Bugzilla::User($cgi->param('userid')) + || ThrowCodeError('invalid_user_id', {'userid' => $cgi->param('userid')}); + my $otherUserID = $otherUser->id(); + my $logoutNeeded = 0; + my @changedFields; + + # Lock tables during the check+update session. + $dbh->bz_lock_tables('profiles WRITE', + 'profiles_activity WRITE', + 'fielddefs READ', + 'namedqueries READ', + 'whine_queries READ', + 'tokens WRITE', + 'logincookies WRITE', + 'groups READ', + 'user_group_map WRITE', + 'group_group_map READ'); + + canSeeUser($otherUserID) + || ThrowUserError('auth_failure', {reason => "not_visible", + action => "modify", + object => "user"}, + 'abort'); + + # Cleanups + my $login = trim($cgi->param('login') || ''); + my $loginold = $cgi->param('loginold') || ''; + my $realname = trim($cgi->param('name') || ''); + my $realnameold = $cgi->param('nameold') || ''; + my $password = $cgi->param('password') || ''; + my $disabledtext = trim($cgi->param('disabledtext') || ''); + my $disabledtextold = $cgi->param('disabledtextold') || ''; + + # Update profiles table entry; silently skip doing this if the user + # is not authorized. + if ($editusers) { + my @values; + + if ($login ne $loginold) { + # Validate, then trick_taint. + $login || ThrowUserError('user_login_required', undef, 'abort'); + CheckEmailSyntax($login); + is_available_username($login) || ThrowUserError('account_exists', + {'email' => $login}, + 'abort'); + trick_taint($login); + push(@changedFields, 'login_name'); + push(@values, $login); + $logoutNeeded = 1; + + # Since we change the login, silently delete any tokens. + $dbh->do('DELETE FROM tokens WHERE userid = ?', {}, $otherUserID); } - elsif ($num == $count-1) { - print " or "; + if ($realname ne $realnameold) { + # The real name may be anything; we use a placeholder for our + # INSERT, and we rely on displaying code to FILTER html. + trick_taint($realname); + push(@changedFields, 'realname'); + push(@values, $realname); } - else { - print ", "; + if ($password) { + # Validate, then trick_taint. + ValidatePassword($password) if $password; + trick_taint($password); + push(@changedFields, 'cryptpassword'); + push(@values, bz_crypt($password)); + $logoutNeeded = 1; } - $num++; - } - PutFooter(); -} - - - -# -# Preliminary checks: -# - -Bugzilla->login(LOGIN_REQUIRED); - -print Bugzilla->cgi->header(); - -$editall = UserInGroup("editusers"); - -$editall - || Bugzilla->user->can_bless - || ThrowUserError("auth_failure", {group => "editusers", - reason => "cant_bless", - action => "edit", - object => "users"}); - - -# -# often used variables -# -my $user = trim($::FORM{user} || ''); -my $action = trim($::FORM{action} || ''); -my $localtrailer = 'edit more users'; -my $candelete = Param('allowuserdeletion'); - -my $dbh = Bugzilla->dbh; - -# -# action='' -> Ask for match string for users. -# - -unless ($action) { - PutHeader("Select match string"); - print qq{ -

- -List users with login name matching: - - -
- -
-}; - PutTrailer(); - exit; -} - - -# -# action='list' -> Show nice list of matching users -# - -if ($action eq 'list') { - PutHeader("Select user"); - my $query = ""; - my $matchstr = $::FORM{'matchstr'}; - if (exists $::FORM{'matchtype'}) { - $query = "SELECT login_name,realname,disabledtext " . - "FROM profiles WHERE login_name "; - if ($::FORM{'matchtype'} eq 'substr') { - $query .= "like"; - $matchstr = '%' . $matchstr . '%'; - } elsif ($::FORM{'matchtype'} eq 'regexp') { - $query .= $dbh->sql_regexp(); - $matchstr = '.' - unless $matchstr; - } elsif ($::FORM{'matchtype'} eq 'notregexp') { - $query .= $dbh->sql_not_regexp(); - $matchstr = '.' - unless $matchstr; - } else { - die "Unknown match type"; - } - $query .= SqlQuote($matchstr) . " ORDER BY login_name"; - } elsif (exists $::FORM{'group'}) { - detaint_natural($::FORM{'group'}); - $query = "SELECT DISTINCT login_name,realname,disabledtext " . - "FROM profiles, user_group_map WHERE profiles.userid = user_group_map.user_id - AND group_id=" . $::FORM{'group'} . " ORDER BY login_name"; - } else { - die "Missing parameters"; - } - - SendSQL($query); - my $count = 0; - my $header = " - - -"; - if ($candelete) { - $header .= "\n"; - } - $header .= "\n"; - print $header; - while ( MoreSQLData() ) { - $count++; - if ($count % 100 == 0) { - print "
Edit user ...Real nameAction
$header"; - } - my ($user, $realname, $disabledtext) = FetchSQLData(); - my $s = ""; - my $e = ""; - if ($disabledtext) { - $s = ''; - $e = ''; + if ($disabledtext ne $disabledtextold) { + # The disable text may be anything; we use a placeholder for our + # INSERT, and we rely on displaying code to FILTER html. + trick_taint($disabledtext); + push(@changedFields, 'disabledtext'); + push(@values, $disabledtext); + $logoutNeeded = 1; } - $realname = ($realname ? html_quote($realname) : "missing"); - print "\n"; - print " $s", html_quote($user), "$e\n"; - print " $s$realname$e\n"; - if ($candelete) { - print " Delete\n"; + if (@changedFields) { + push (@values, $otherUserID); + $logoutNeeded && Bugzilla->logout_user_by_id($otherUserID); + $dbh->do('UPDATE profiles SET ' . + join(' = ?,', @changedFields).' = ? ' . + 'WHERE userid = ?', + undef, @values); + # FIXME: should create profiles_activity entries. } - print ""; - } - if ($editall) { - print "\n"; - my $span = $candelete ? 3 : 2; - print qq{ - - add a new user - -}; - print ""; } - print "\n"; - print "$count users found.\n"; - - PutTrailer($localtrailer); - exit; -} - - - - -# -# action='add' -> present form for parameters for new user -# -# (next action will be 'new') -# - -if ($action eq 'add') { - $editall || ThrowUserError("auth_failure", {group => "editusers", - action => "add", - object => "users"}); - PutHeader("Add user"); - print "
\n"; - print "\n"; - - EmitFormElements(0, '', '', ''); - - print "
\n
\n"; - print "\n"; - print "\n"; - print "
"; - - my $other = $localtrailer; - $other =~ s/more/other/; - PutTrailer($other); - exit; -} + # Update group settings. + my $sth_add_mapping = $dbh->prepare( + qq{INSERT INTO user_group_map ( + user_id, group_id, isbless, grant_type + ) VALUES ( + ?, ?, ?, ? + ) + }); + my $sth_remove_mapping = $dbh->prepare( + qq{DELETE FROM user_group_map + WHERE user_id = ? + AND group_id = ? + AND isbless = ? + AND grant_type = ? + }); + + # We need the group names, too -- for display and for profiles_activity. + my $groups = $dbh->selectall_hashref('SELECT id, name FROM groups', 'id'); + my @groupsAddedTo; + my @groupsRemovedFrom; + my @groupsGrantedRightsToBless; + my @groupsDeniedRightsToBless; + + # Regard only groups the user is allowed to bless and skip all others + # silently. + # FIXME: checking for existence of each user_group_map entry + # would allow to display a friendlier error message on page reloads. + foreach (@{groupsUserMayBless($user, 'id')}) { + my $id = $$_{'id'}; + + # Change memberships. + my $oldgroupid = $cgi->param("oldgroup_$id") || '0'; + my $groupid = $cgi->param("group_$id") || '0'; + if ($groupid ne $oldgroupid) { + if ($groupid eq '0') { + $sth_remove_mapping->execute( + $otherUserID, $id, 0, GRANT_DIRECT); + push(@groupsRemovedFrom, $$groups{$id}{'name'}); + } else { + $sth_add_mapping->execute( + $otherUserID, $id, 0, GRANT_DIRECT); + push(@groupsAddedTo, $$groups{$id}{'name'}); + } + } - -# -# action='new' -> add user entered in the 'action=add' screen -# - -if ($action eq 'new') { - $editall || ThrowUserError("auth_failure", {group => "editusers", - action => "add", - object => "users"}); - - # Cleanups and valididy checks - my $realname = trim($::FORM{realname} || ''); - # We don't trim the password since that could falsely lead the user - # to believe a password with a space was accepted even though a space - # is an illegal character in a Bugzilla password. - my $password = $::FORM{'password'}; - my $disabledtext = trim($::FORM{disabledtext} || ''); - my $emailregexp = Param("emailregexp"); - - PutHeader("Adding new user"); - unless ($user) { - print "You must enter a name for the new user. Please press\n"; - print "Back and try again.\n"; - PutTrailer($localtrailer); - exit; - } - unless ($user =~ m/$emailregexp/) { - print "The user name entered must be a valid e-mail address. Please press\n"; - print "Back and try again.\n"; - PutTrailer($localtrailer); - exit; - } - if (!is_available_username($user)) { - print "The user '$user' does already exist. Please press\n"; - print "Back and try again.\n"; - PutTrailer($localtrailer); - exit; + # Only members of the editusers group may change bless grants. + # Skip silently if this is not the case. + if ($editusers) { + my $oldgroupid = $cgi->param("oldbless_$id") || '0'; + my $groupid = $cgi->param("bless_$id") || '0'; + if ($groupid ne $oldgroupid) { + if ($groupid eq '0') { + $sth_remove_mapping->execute( + $otherUserID, $id, 1, GRANT_DIRECT); + push(@groupsDeniedRightsToBless, $$groups{$id}{'name'}); + } else { + $sth_add_mapping->execute( + $otherUserID, $id, 1, GRANT_DIRECT); + push(@groupsGrantedRightsToBless, $$groups{$id}{'name'}); + } + } + } } - my $passworderror = ValidatePassword($password); - if ( $passworderror ) { - print $passworderror; - PutTrailer($localtrailer); - exit; + if (@groupsAddedTo || @groupsRemovedFrom) { + $dbh->do(qq{INSERT INTO profiles_activity ( + userid, who, + profiles_when, fieldid, + oldvalue, newvalue + ) VALUES ( + ?, ?, now(), ?, ?, ? + ) + }, + undef, + ($otherUserID, $userid, + GetFieldID('bug_group'), + join(', ', @groupsRemovedFrom), join(', ', @groupsAddedTo))); + $dbh->do('UPDATE profiles SET refreshed_when=? WHERE userid = ?', + undef, ('1900-01-01 00:00:00', $otherUserID)); } - - # Add the new user - SendSQL("INSERT INTO profiles ( " . - "login_name, cryptpassword, realname, " . - "emailflags, disabledtext" . - " ) VALUES ( " . - SqlQuote($user) . "," . - SqlQuote(bz_crypt($password)) . "," . - SqlQuote($realname) . "," . - SqlQuote(Bugzilla::Constants::DEFAULT_EMAIL_SETTINGS) . "," . - SqlQuote($disabledtext) . ")" ); - - #+++ send e-mail away - - print "OK, done.
\n"; - my $newuserid = $dbh->bz_last_key('profiles', 'userid'); - - my $changeduser = new Bugzilla::User($newuserid); - $changeduser->derive_groups(); - print "To change ${user}'s permissions, go back and " . - "edit this user."; - print "

\n"; - PutTrailer($localtrailer); - exit; - -} - - - -# -# action='del' -> ask if user really wants to delete -# -# (next action would be 'delete') -# - -if ($action eq 'del') { - $candelete || ThrowUserError("users_deletion_disabled"); - $editall || ThrowUserError("auth_failure", {group => "editusers", + # FIXME: should create profiles_activity entries for blesser changes. + + $dbh->bz_unlock_tables(); + + # FIXME: userDataToVars may be off when editing ourselves. + userDataToVars($otherUserID); + + $vars->{'message'} = 'account_updated'; + $vars->{'loginold'} = $loginold; + $vars->{'changed_fields'} = \@changedFields; + $vars->{'groups_added_to'} = \@groupsAddedTo; + $vars->{'groups_removed_from'} = \@groupsRemovedFrom; + $vars->{'groups_granted_rights_to_bless'} = \@groupsGrantedRightsToBless; + $vars->{'groups_denied_rights_to_bless'} = \@groupsDeniedRightsToBless; + $template->process('admin/users/edit.html.tmpl', $vars) + || ThrowTemplateError($template->error()); + +########################################################################### +} elsif ($action eq 'del') { + my $otherUser = new Bugzilla::User($cgi->param('userid')) + || ThrowCodeError('invalid_user_id', {'userid' => $cgi->param('userid')}); + my $otherUserID = $otherUser->id(); + + Param('allowuserdeletion') || ThrowUserError('users_deletion_disabled'); + $editusers || ThrowUserError('auth_failure', {group => "editusers", + action => "delete", + object => "users"}); + canSeeUser($otherUserID) || ThrowUserError('auth_failure', + {reason => "not_visible", action => "delete", - object => "users"}); - CheckUser($user); - - # display some data about the user - SendSQL("SELECT userid, realname FROM profiles - WHERE login_name=" . SqlQuote($user)); - my ($thisuserid, $realname) = - FetchSQLData(); - $realname = ($realname ? html_quote($realname) : "missing"); - - PutHeader("Delete user $user"); - print "\n"; - print "\n"; - print " \n"; - print " \n"; - - print "\n"; - print " \n"; - print " \n"; - - print "\n"; - print " \n"; - print " \n"; - - print "\n"; - print " \n"; - print " \n"; - - - # Check if the user is an initialowner - my $nodelete = ''; - - SendSQL("SELECT products.name, components.name " . - "FROM products, components " . - "WHERE products.id = components.product_id " . - " AND initialowner=" . login_to_id($user)); - $found = 0; - while (MoreSQLData()) { - if ($found) { - print "
\n"; - } else { - print "\n"; - print " \n"; - print " \n" if $found; - - - # Check if the user is an initialqacontact - - SendSQL("SELECT products.name, components.name " . - "FROM products, components " . - "WHERE products.id = components.product_id " . - " AND initialqacontact=" . login_to_id($user)); - $found = 0; - while (MoreSQLData()) { - if ($found) { - print "
\n"; - } else { - print "\n"; - print " \n"; - print " \n" if $found; - - print "
PartValue
Login name:$user
Real name:$realname
Group set:"; - SendSQL("SELECT name - FROM groups, user_group_map - WHERE groups.id = user_group_map.group_id - AND user_group_map.user_id = $thisuserid - AND isbless = 0 - ORDER BY name"); - my $found = 0; - while ( MoreSQLData() ) { - my ($name) = FetchSQLData(); - print "
\n" if $found; - print ucfirst $name; - $found = 1; - } - print "none" unless $found; - print "
Initial owner:"; - } - my ($product, $component) = FetchSQLData(); - print "$product: $component"; - $found = 1; - $nodelete = 'initial bug owner'; - } - print "
Initial QA contact:"; - } - my ($product, $component) = FetchSQLData(); - print "$product: $component"; - $found = 1; - $nodelete = 'initial QA contact'; + object => "user"}); + + $vars->{'otheruser'} = $otherUser; + $vars->{'editcomponents'} = UserInGroup('editcomponents'); + + # If the user is initial owner or initial QA contact of a component, + # then no deletion is possible. + $vars->{'product_responsibilities'} = productResponsibilities($otherUserID); + + # Find other cross references. + $vars->{'bugs'} = $dbh->selectrow_array( + qq{SELECT COUNT(*) + FROM bugs + WHERE assigned_to = ? OR + qa_contact = ? OR + reporter = ? + }, + undef, ($otherUserID, $otherUserID, $otherUserID)); + $vars->{'cc'} = $dbh->selectrow_array( + 'SELECT COUNT(*) FROM cc WHERE who = ?', + undef, $otherUserID); + $vars->{'bugs_activity'} = $dbh->selectrow_array( + 'SELECT COUNT(*) FROM bugs_activity WHERE who = ?', + undef, $otherUserID); + $vars->{'flags'}{'requestee'} = $dbh->selectrow_array( + 'SELECT COUNT(*) FROM flags WHERE requestee_id = ?', + undef, $otherUserID); + $vars->{'flags'}{'setter'} = $dbh->selectrow_array( + 'SELECT COUNT(*) FROM flags WHERE setter_id = ?', + undef, $otherUserID); + $vars->{'groups'} = $dbh->selectall_arrayref( + qq{SELECT name + FROM groups, user_group_map + WHERE id = group_id + AND user_id = ? + AND isbless = 0 + ORDER BY name + }, + {'Slice' => {}}, $otherUserID); + $vars->{'longdescs'} = $dbh->selectrow_array( + 'SELECT COUNT(*) FROM longdescs WHERE who = ?', + undef, $otherUserID); + $vars->{'namedqueries'} = $dbh->selectrow_array( + 'SELECT COUNT(*) FROM namedqueries WHERE userid = ?', + undef, $otherUserID); + $vars->{'profiles_activity'} = $dbh->selectrow_array( + 'SELECT COUNT(*) FROM profiles_activity WHERE who = ? AND userid != ?', + undef, ($otherUserID, $otherUserID)); + $vars->{'series'} = $dbh->selectrow_array( + 'SELECT COUNT(*) FROM series WHERE creator = ?', + undef, $otherUserID); + $vars->{'votes'} = $dbh->selectrow_array( + 'SELECT COUNT(*) FROM votes WHERE who = ?', + undef, $otherUserID); + $vars->{'watch'}{'watched'} = $dbh->selectrow_array( + 'SELECT COUNT(*) FROM watch WHERE watched = ?', + undef, $otherUserID); + $vars->{'watch'}{'watcher'} = $dbh->selectrow_array( + 'SELECT COUNT(*) FROM watch WHERE watcher = ?', + undef, $otherUserID); + $vars->{'whine_events'} = $dbh->selectrow_array( + 'SELECT COUNT(*) FROM whine_events WHERE owner_userid = ?', + undef, $otherUserID); + $vars->{'whine_schedules'} = $dbh->selectrow_array( + qq{SELECT COUNT(distinct eventid) + FROM whine_schedules + WHERE mailto = ? + AND mailto_type = ? + }, + undef, ($otherUserID, MAILTO_USER)); + + $template->process('admin/users/confirm-delete.html.tmpl', $vars) + || ThrowTemplateError($template->error()); + +########################################################################### +} elsif ($action eq 'delete') { + my $otherUser = new Bugzilla::User($cgi->param('userid')) + || ThrowCodeError('invalid_user_id', {'userid' => $cgi->param('userid')}); + my $otherUserID = $otherUser->id(); + my $otherUserLogin = $otherUser->login(); + + # Lock tables during the check+removal session. + # FIXME: if there was some change on these tables after the deletion + # confirmation checks, we may do something here we haven't warned + # about. + $dbh->bz_lock_tables('products READ', + 'components READ', + 'logincookies WRITE', + 'profiles WRITE', + 'profiles_activity WRITE', + 'groups READ', + 'user_group_map WRITE', + 'group_group_map READ', + 'flags WRITE', + 'cc WRITE', + 'namedqueries WRITE', + 'tokens WRITE', + 'votes WRITE', + 'watch WRITE', + 'series WRITE', + 'series_data WRITE', + 'whine_schedules WRITE', + 'whine_queries WRITE', + 'whine_events WRITE'); + + Param('allowuserdeletion') + || ThrowUserError('users_deletion_disabled', undef, 'abort'); + $editusers || ThrowUserError('auth_failure', + {group => "editusers", + action => "delete", + object => "users"}, + 'abort'); + canSeeUser($otherUserID) || ThrowUserError('auth_failure', + {reason => "not_visible", + action => "delete", + object => "user"}, + 'abort'); + productResponsibilities($otherUserID) + && ThrowUserError('user_has_responsibility', undef, 'abort'); + + Bugzilla->logout_user_by_id($otherUserID); + + # Reference removals. + $dbh->do('UPDATE flags set requestee_id = NULL WHERE requestee_id = ?', + undef, $otherUserID); + + # Simple deletions in referred tables. + $dbh->do('DELETE FROM cc WHERE who = ?', undef, $otherUserID); + $dbh->do('DELETE FROM logincookies WHERE userid = ?', undef, $otherUserID); + $dbh->do('DELETE FROM namedqueries WHERE userid = ?', undef, $otherUserID); + $dbh->do('DELETE FROM profiles_activity WHERE userid = ? OR who = ?', undef, + ($otherUserID, $otherUserID)); + $dbh->do('DELETE FROM tokens WHERE userid = ?', undef, $otherUserID); + $dbh->do('DELETE FROM user_group_map WHERE user_id = ?', undef, + $otherUserID); + $dbh->do('DELETE FROM votes WHERE who = ?', undef, $otherUserID); + $dbh->do('DELETE FROM watch WHERE watcher = ? OR watched = ?', undef, + ($otherUserID, $otherUserID)); + + # More complex deletions in referred tables. + my $id; + + # 1) Series + my $sth_seriesid = $dbh->prepare( + 'SELECT series_id FROM series WHERE creator = ?'); + my $sth_deleteSeries = $dbh->prepare( + 'DELETE FROM series WHERE series_id = ?'); + my $sth_deleteSeriesData = $dbh->prepare( + 'DELETE FROM series_data WHERE series_id = ?'); + + $sth_seriesid->execute($otherUserID); + while ($id = $sth_seriesid->fetchrow_array()) { + $sth_deleteSeriesData->execute($id); + $sth_deleteSeries->execute($id); } - print "
\n"; + # 2) Whines + my $sth_whineidFromSchedules = $dbh->prepare( + qq{SELECT eventid FROM whine_schedules + WHERE mailto = ? AND mailto_type = ?}); + my $sth_whineidFromEvents = $dbh->prepare( + 'SELECT id FROM whine_events WHERE owner_userid = ?'); + my $sth_deleteWhineEvent = $dbh->prepare( + 'DELETE FROM whine_events WHERE id = ?'); + my $sth_deleteWhineQuery = $dbh->prepare( + 'DELETE FROM whine_queries WHERE eventid = ?'); + my $sth_deleteWhineSchedule = $dbh->prepare( + 'DELETE FROM whine_schedules WHERE eventid = ?'); + + $sth_whineidFromSchedules->execute($otherUserID, MAILTO_USER); + while ($id = $sth_whineidFromSchedules->fetchrow_array()) { + $sth_deleteWhineQuery->execute($id); + $sth_deleteWhineSchedule->execute($id); + $sth_deleteWhineEvent->execute($id); + } - if ($nodelete) { - print "

You can't delete this user because '$user' is an $nodelete ", - "for at least one product."; - PutTrailer($localtrailer); - exit; + $sth_whineidFromEvents->execute($otherUserID); + while ($id = $sth_whineidFromEvents->fetchrow_array()) { + $sth_deleteWhineQuery->execute($id); + $sth_deleteWhineSchedule->execute($id); + $sth_deleteWhineEvent->execute($id); } + # Finally, remove the user account itself. + $dbh->do('DELETE FROM profiles WHERE userid = ?', undef, $otherUserID); - print "

Confirmation

\n"; - print "

Do you really want to delete this user?

\n"; + $dbh->bz_unlock_tables(); - print "

\n"; - print "\n"; - print "\n"; - print "\n"; - print "
"; + $vars->{'message'} = 'account_deleted'; + $vars->{'otheruser'}{'login'} = $otherUserLogin; + $vars->{'restrictablegroups'} = groupsUserMayBless($user, 'id', 'name'); + $template->process('admin/users/search.html.tmpl', $vars) + || ThrowTemplateError($template->error()); - PutTrailer($localtrailer); - exit; +########################################################################### +} else { + $vars->{'action'} = $action; + ThrowCodeError('action_unrecognized', $vars); } +exit; +########################################################################### +# Helpers +########################################################################### -# -# action='delete' -> really delete the user -# - -if ($action eq 'delete') { - $candelete || ThrowUserError("users_deletion_disabled"); - $editall || ThrowUserError("auth_failure", {group => "editusers", - action => "delete", - object => "users"}); - CheckUser($user); - - SendSQL("SELECT userid - FROM profiles - WHERE login_name=" . SqlQuote($user)); - my $userid = FetchOneColumn(); - - Bugzilla->logout_user_by_id($userid); - SendSQL("DELETE FROM profiles - WHERE login_name=" . SqlQuote($user)); - SendSQL("DELETE FROM user_group_map - WHERE user_id=" . $userid); - - PutHeader("Deleting user"); - print "User deleted.
\n"; - PutTrailer($localtrailer); - exit; +# Copy incoming list selection values from CGI params to template variables. +sub mirrorListSelectionValues { + if (defined($cgi->param('matchtype'))) { + foreach ('matchstr', 'matchtype', 'grouprestrict', 'groupid') { + $vars->{'listselectionvalues'}{$_} = $cgi->param($_); + } + } } +# Give a list of IDs of groups the user can see. +sub visibleGroupsAsString { + return join(', ', @{$user->visible_groups_direct()}); +} +# Give a list of IDs of groups the user may bless. +sub groupsUserMayBless { + my $user = shift; + my $fieldList = join(', ', @_); + my $query; + my $connector; + my @bindValues; -# -# action='edit' -> present the user edit from -# -# (next action would be 'update') -# - -if ($action eq 'edit') { - PutHeader("Edit user $user"); - CheckUser($user); - - # get data of user - SendSQL("SELECT userid, realname, disabledtext - FROM profiles - WHERE login_name=" . SqlQuote($user)); - my ($thisuserid, $realname, $disabledtext) = FetchSQLData(); + $user->derive_groups(1); - if ($thisuserid > 0) { - # Force groups to be up to date - my $changeduser = new Bugzilla::User($thisuserid); - $changeduser->derive_groups(); - } - print "
\n"; - print "\n"; - - EmitFormElements($thisuserid, $user, $realname, $disabledtext); - - print "
\n"; - print "\n"; - print "\n"; - print "\n"; - print "\n"; - print "\n"; - print "
User is a member of any groups shown with a check or grey bar. - A grey bar indicates indirect membership, either derived from other - groups (marked with square brackets) or via regular expression - (marked with '*').

- Square brackets around the bless checkbox indicate the ability - to bless users (grant them membership in the group) as a result - of membership in another group. -
"; - - print "

"; - if ($candelete) { - print "
\n"; - print "\n"; - print "\n"; - print "\n"; - print "
"; + if ($editusers) { + $query = "SELECT DISTINCT $fieldList FROM groups"; + $connector = 'WHERE'; + } else { + $query = qq{SELECT DISTINCT $fieldList + FROM groups, user_group_map AS ugm + LEFT JOIN group_group_map AS ggm + ON ggm.member_id = ugm.group_id + AND ggm.grant_type = ? + WHERE user_id = ? + AND ((id = group_id AND isbless = 1) OR + (id = grantor_id)) + }; + @bindValues = (GROUP_BLESS, $userid); + $connector = 'AND'; } - my $x = $localtrailer; - $x =~ s/more/other/; - PutTrailer($x); - exit; -} - -# -# action='update' -> update the user -# - -if ($action eq 'update') { - my $userold = trim($::FORM{userold} || ''); - my $realname = trim($::FORM{realname} || ''); - my $realnameold = trim($::FORM{realnameold} || ''); - my $password = $::FORM{password} || ''; - my $disabledtext = trim($::FORM{disabledtext} || ''); - my $disabledtextold = trim($::FORM{disabledtextold} || ''); - my @localtrailers = ($localtrailer); - $localtrailer = qq|edit user again|; - PutHeader("Updating user $userold" . ($realnameold && " ($realnameold)")); - - CheckUser($userold); - SendSQL("SELECT userid FROM profiles - WHERE login_name=" . SqlQuote($userold)); - my ($thisuserid) = FetchSQLData(); - - my $emailregexp = Param("emailregexp"); - unless ($user =~ m/$emailregexp/) { - print "The user name entered must be a valid e-mail address. Please press\n"; - print "Back and try again.\n"; - PutTrailer($localtrailer); - exit; + # If visibilitygroups are used, restrict the set of groups. + if (Param('usevisibilitygroups')) { + my $visibleGroups = visibleGroupsAsString(); + $query .= " $connector id in ($visibleGroups)"; } - my @grpadd = (); - my @grpdel = (); - my $chggrp = 0; - SendSQL("SELECT id, name FROM groups"); - while (my ($groupid, $name) = FetchSQLData()) { - next unless ($editall || Bugzilla->user->can_bless($name)); - if ($::FORM{"oldgroup_$groupid"} != ($::FORM{"group_$groupid"} ? 1 : 0)) { - # group membership changed - PushGlobalSQLState(); - $chggrp = 1; - SendSQL("DELETE FROM user_group_map - WHERE user_id = $thisuserid - AND group_id = $groupid - AND isbless = 0 - AND grant_type = " . GRANT_DIRECT); - if ($::FORM{"group_$groupid"}) { - SendSQL("INSERT INTO user_group_map - (user_id, group_id, isbless, grant_type) - VALUES ($thisuserid, $groupid, 0," . GRANT_DIRECT . ")"); - print "Added user to group $name
\n"; - push(@grpadd, $name); - } else { - print "Dropped user from group $name
\n"; - push(@grpdel, $name); - } - PopGlobalSQLState(); - } - if ($editall && ($::FORM{"oldbless_$groupid"} != ($::FORM{"bless_$groupid"} ? 1 : 0))) { - # group membership changed - PushGlobalSQLState(); - SendSQL("DELETE FROM user_group_map - WHERE user_id = $thisuserid - AND group_id = $groupid - AND isbless = 1 - AND grant_type = " . GRANT_DIRECT); - if ($::FORM{"bless_$groupid"}) { - SendSQL("INSERT INTO user_group_map - (user_id, group_id, isbless, grant_type) - VALUES ($thisuserid, $groupid, 1," . GRANT_DIRECT . ")"); - print "Granted user permission to bless group $name
\n"; - } else { - print "Revoked user's permission to bless group $name
\n"; - } - PopGlobalSQLState(); - - } - } - my $fieldid = GetFieldID("bug_group"); - if ($chggrp) { - SendSQL("INSERT INTO profiles_activity " . - "(userid, who, profiles_when, fieldid, oldvalue, newvalue) " . - "VALUES " . "($thisuserid, $::userid, now(), $fieldid, " . - SqlQuote(join(", ",@grpdel)) . ", " . - SqlQuote(join(", ",@grpadd)) . ")"); - SendSQL("UPDATE profiles SET refreshed_when='1900-01-01 00:00:00' " . - "WHERE userid = $thisuserid"); - } + $query .= ' ORDER BY name'; + return $dbh->selectall_arrayref($query, {'Slice' => {}}, @bindValues); +} - # Update the database with the user's new password if they changed it. - if ( $editall && $password ) { - my $passworderror = ValidatePassword($password); - if ( !$passworderror ) { - my $cryptpassword = SqlQuote(bz_crypt($password)); - my $loginname = SqlQuote($userold); - SendSQL("UPDATE profiles - SET cryptpassword = $cryptpassword - WHERE login_name = $loginname"); - SendSQL("SELECT userid - FROM profiles - WHERE login_name=" . SqlQuote($userold)); - my $userid = FetchOneColumn(); - Bugzilla->logout_user_by_id($userid); - print "Updated password.
\n"; - } else { - print "Did not update password: $passworderror
\n"; - } - } - if ($editall && $realname ne $realnameold) { - SendSQL("UPDATE profiles - SET realname=" . SqlQuote($realname) . " - WHERE login_name=" . SqlQuote($userold)); - print 'Updated real name to ' . html_quote($realname) . ".
\n"; - } - if ($editall && $disabledtext ne $disabledtextold) { - SendSQL("UPDATE profiles - SET disabledtext=" . SqlQuote($disabledtext) . " - WHERE login_name=" . SqlQuote($userold)); - SendSQL("SELECT userid - FROM profiles - WHERE login_name=" . SqlQuote($userold)); - my $userid = FetchOneColumn(); - Bugzilla->logout_user_by_id($userid); - print "Updated disabled text.
\n"; +# Determine whether the user can see a user. (Checks for existence, too.) +sub canSeeUser { + my $otherUserID = shift; + my $query; + + if (Param('usevisibilitygroups')) { + my $visibleGroups = visibleGroupsAsString(); + $query = qq{SELECT COUNT(DISTINCT userid) + FROM profiles, user_group_map + WHERE userid = ? + AND user_id = userid + AND isbless = 0 + AND group_id IN ($visibleGroups) + }; + } else { + $query = qq{SELECT COUNT(userid) + FROM profiles + WHERE userid = ? + }; } - if ($editall && $user ne $userold) { - unless ($user) { - print "Sorry, I can't delete the user's name."; - $userold = url_quote($userold); - $localtrailer =~ s/XXX/$userold/; - push @localtrailers, $localtrailer; - PutTrailer(@localtrailers); - exit; - } - if (TestUser($user)) { - print "Sorry, user name '$user' is already in use."; - $userold = url_quote($userold); - $localtrailer =~ s/XXX/$userold/; - push @localtrailers, $localtrailer; - PutTrailer($localtrailer); - exit; - } - - SendSQL("UPDATE profiles - SET login_name=" . SqlQuote($user) . " - WHERE login_name=" . SqlQuote($userold)); + return $dbh->selectrow_array($query, undef, $otherUserID); +} - print q|Updated user's name to ' . html_quote($user) . ".
\n"; +# Retrieve product responsibilities, usable for both display and verification. +sub productResponsibilities { + my $userid = shift; + my $h = $dbh->selectall_arrayref( + qq{SELECT products.name AS productname, + components.name AS componentname, + initialowner, + initialqacontact + FROM products, components + WHERE products.id = components.product_id + AND ? IN (initialowner, initialqacontact) + }, + {'Slice' => {}}, $userid); + + if (@$h) { + return $h; + } else { + return undef; } - my $changeduser = new Bugzilla::User($thisuserid); - $changeduser->derive_groups(); - - $user = url_quote($user); - $localtrailer =~ s/XXX/$user/; - push @localtrailers, $localtrailer; - PutTrailer(@localtrailers); - exit; } - - -# -# No valid action found -# - -PutHeader("Error"); -print "I don't have a clue what you want.
\n"; +# Retrieve user data for the user editing form. User creation and user +# editing code rely on this to call derive_groups(). +sub userDataToVars { + my $userid = shift; + my $user = new Bugzilla::User($userid); + my $query; + + $user->derive_groups(); + + $vars->{'otheruser'} = $user; + $vars->{'groups'} = groupsUserMayBless($user, 'id', 'name', 'description'); + $vars->{'disabledtext'} = $dbh->selectrow_array( + 'SELECT disabledtext FROM profiles WHERE userid = ?', undef, $userid); + + $vars->{'permissions'} = $dbh->selectall_hashref( + qq{SELECT id, + COUNT(directmember.group_id) AS directmember, + COUNT(regexpmember.group_id) AS regexpmember, + COUNT(derivedmember.group_id) AS derivedmember, + COUNT(directbless.group_id) AS directbless + FROM groups + LEFT JOIN user_group_map AS directmember + ON directmember.group_id = id + AND directmember.user_id = ? + AND directmember.isbless = 0 + AND directmember.grant_type = ? + LEFT JOIN user_group_map AS regexpmember + ON regexpmember.group_id = id + AND regexpmember.user_id = ? + AND regexpmember.isbless = 0 + AND regexpmember.grant_type = ? + LEFT JOIN user_group_map AS derivedmember + ON derivedmember.group_id = id + AND derivedmember.user_id = ? + AND derivedmember.isbless = 0 + AND derivedmember.grant_type = ? + LEFT JOIN user_group_map AS directbless + ON directbless.group_id = id + AND directbless.user_id = ? + AND directbless.isbless = 1 + AND directbless.grant_type = ? + GROUP BY id + }, + 'id', undef, + ($userid, GRANT_DIRECT, + $userid, GRANT_REGEXP, + $userid, GRANT_DERIVED, + $userid, GRANT_DIRECT)); + + # Find indirect bless permission. + $query = qq{SELECT groups.id + FROM groups, user_group_map AS ugm, group_group_map AS ggm + WHERE ugm.user_id = ? + AND groups.id = ggm.grantor_id + AND ggm.member_id = ugm.group_id + AND ugm.isbless = 0 + AND ggm.grant_type = ? + GROUP BY id + }; + foreach (@{$dbh->selectall_arrayref($query, undef, ($userid, GROUP_BLESS))}) { + # Merge indirect bless permissions into permission variable. + $vars->{'permissions'}{${$_}[0]}{'indirectbless'} = 1; + } +} -- cgit v1.2.3-24-g4f1b